1 /*
   2  * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap;
  27 
  28 
  29 import java.util.Enumeration;
  30 import java.util.Vector;
  31 
  32 import javax.naming.*;
  33 import javax.naming.directory.Attributes;
  34 import javax.naming.directory.Attribute;
  35 import javax.naming.directory.BasicAttributes;
  36 
  37 
  38 /**
  39  * <code>LdapName</code> implements compound names for LDAP v3 as
  40  * specified by RFC 2253.
  41  *<p>
  42  * RFC 2253 has a few ambiguities and outright inconsistencies.  These
  43  * are resolved as follows:
  44  * <ul>
  45  * <li> RFC 2253 leaves the term "whitespace" undefined.  The
  46  *      definition of "optional-space" given in RFC 1779 is used in
  47  *      its place:  either a space character or a carriage return ("\r").
  48  * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'.
  49  *      Such whitespace is accepted but not generated by this code,
  50  *      and is ignored when comparing names.
  51  * <li> AttributeValue strings containing '=' or non-leading '#'
  52  *      characters (unescaped) are accepted.
  53  * </ul>
  54  *<p>
  55  * String names passed to <code>LdapName</code> or returned by it
  56  * use the full 16-bit Unicode character set.  They may also contain
  57  * characters encoded into UTF-8 with each octet represented by a
  58  * three-character substring such as "\\B4".
  59  * They may not, however, contain characters encoded into UTF-8 with
  60  * each octet represented by a single character in the string:  the
  61  * meaning would be ambiguous.
  62  *<p>
  63  * <code>LdapName</code> will properly parse all valid names, but
  64  * does not attempt to detect all possible violations when parsing
  65  * invalid names.  It's "generous".
  66  *<p>
  67  * When names are tested for equality, attribute types and binary
  68  * values are case-insensitive, and string values are by default
  69  * case-insensitive.
  70  * String values with different but equivalent usage of quoting,
  71  * escaping, or UTF8-hex-encoding are considered equal.  The order of
  72  * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not
  73  * significant.
  74  *
  75  * @author Scott Seligman
  76  */
  77 
  78 public final class LdapName implements Name {
  79 
  80     private transient String unparsed;  // if non-null, the DN in unparsed form
  81     private transient Vector rdns;      // parsed name components
  82     private transient boolean valuesCaseSensitive = false;
  83 
  84     /**
  85      * Constructs an LDAP name from the given DN.
  86      *
  87      * @param name      An LDAP DN.  To JNDI, a compound name.
  88      *
  89      * @throws InvalidNameException if a syntax violation is detected.
  90      */
  91     public LdapName(String name) throws InvalidNameException {
  92         unparsed = name;
  93         parse();
  94     }
  95 
  96     /*
  97      * Constructs an LDAP name given its parsed components and, optionally
  98      * (if "name" is not null), the unparsed DN.
  99      */
 100     private LdapName(String name, Vector rdns) {
 101         unparsed = name;
 102         this.rdns = (Vector)rdns.clone();
 103     }
 104 
 105     /*
 106      * Constructs an LDAP name given its parsed components (the elements
 107      * of "rdns" in the range [beg,end)) and, optionally
 108      * (if "name" is not null), the unparsed DN.
 109      */
 110     private LdapName(String name, Vector rdns, int beg, int end) {
 111         unparsed = name;
 112         this.rdns = new Vector();
 113         for (int i = beg; i < end; i++) {
 114             this.rdns.addElement(rdns.elementAt(i));
 115         }
 116     }
 117 
 118 
 119     public Object clone() {
 120         return new LdapName(unparsed, rdns);
 121     }
 122 
 123     public String toString() {
 124         if (unparsed != null) {
 125             return unparsed;
 126         }
 127 
 128         StringBuffer buf = new StringBuffer();
 129         for (int i = rdns.size() - 1; i >= 0; i--) {
 130             if (i < rdns.size() - 1) {
 131                 buf.append(',');
 132             }
 133             Rdn rdn = (Rdn)rdns.elementAt(i);
 134             buf.append(rdn);
 135         }
 136 
 137         unparsed = new String(buf);
 138         return unparsed;
 139     }
 140 
 141     public boolean equals(Object obj) {
 142         return ((obj instanceof LdapName) &&
 143                 (compareTo(obj) == 0));
 144     }
 145 
 146     public int compareTo(Object obj) {
 147         LdapName that = (LdapName)obj;
 148 
 149         if ((obj == this) ||                    // check possible shortcuts
 150             (unparsed != null && unparsed.equals(that.unparsed))) {
 151             return 0;
 152         }
 153 
 154         // Compare RDNs one by one, lexicographically.
 155         int minSize = Math.min(rdns.size(), that.rdns.size());
 156         for (int i = 0 ; i < minSize; i++) {
 157             // Compare a single pair of RDNs.
 158             Rdn rdn1 = (Rdn)rdns.elementAt(i);
 159             Rdn rdn2 = (Rdn)that.rdns.elementAt(i);
 160 
 161             int diff = rdn1.compareTo(rdn2);
 162             if (diff != 0) {
 163                 return diff;
 164             }
 165         }
 166         return (rdns.size() - that.rdns.size());        // longer DN wins
 167     }
 168 
 169     public int hashCode() {
 170         // Sum up the hash codes of the components.
 171         int hash = 0;
 172 
 173         // For each RDN...
 174         for (int i = 0; i < rdns.size(); i++) {
 175             Rdn rdn = (Rdn)rdns.elementAt(i);
 176             hash += rdn.hashCode();
 177         }
 178         return hash;
 179     }
 180 
 181     public int size() {
 182         return rdns.size();
 183     }
 184 
 185     public boolean isEmpty() {
 186         return rdns.isEmpty();
 187     }
 188 
 189     public Enumeration getAll() {
 190         final Enumeration enum_ = rdns.elements();
 191 
 192         return new Enumeration () {
 193             public boolean hasMoreElements() {
 194                 return enum_.hasMoreElements();
 195             }
 196             public Object nextElement() {
 197                 return enum_.nextElement().toString();
 198             }
 199         };
 200     }
 201 
 202     public String get(int pos) {
 203         return rdns.elementAt(pos).toString();
 204     }
 205 
 206     public Name getPrefix(int pos) {
 207         return new LdapName(null, rdns, 0, pos);
 208     }
 209 
 210     public Name getSuffix(int pos) {
 211         return new LdapName(null, rdns, pos, rdns.size());
 212     }
 213 
 214     public boolean startsWith(Name n) {
 215         int len1 = rdns.size();
 216         int len2 = n.size();
 217         return (len1 >= len2 &&
 218                 matches(0, len2, n));
 219     }
 220 
 221     public boolean endsWith(Name n) {
 222         int len1 = rdns.size();
 223         int len2 = n.size();
 224         return (len1 >= len2 &&
 225                 matches(len1 - len2, len1, n));
 226     }
 227 
 228     /**
 229      * Controls whether string-values are treated as case-sensitive
 230      * when the string values within names are compared.  The default
 231      * behavior is case-insensitive comparison.
 232      */
 233      public void setValuesCaseSensitive(boolean caseSensitive) {
 234          toString();
 235          rdns = null;   // clear any cached information
 236          try {
 237              parse();
 238          } catch (InvalidNameException e) {
 239              // shouldn't happen
 240              throw new IllegalStateException("Cannot parse name: " + unparsed);
 241          }
 242          valuesCaseSensitive = caseSensitive;
 243      }
 244 
 245     /*
 246      * Helper method for startsWith() and endsWith().
 247      * Returns true if components [beg,end) match the components of "n".
 248      * If "n" is not an LdapName, each of its components is parsed as
 249      * the string form of an RDN.
 250      * The following must hold:  end - beg == n.size().
 251      */
 252     private boolean matches(int beg, int end, Name n) {
 253         for (int i = beg; i < end; i++) {
 254             Rdn rdn;
 255             if (n instanceof LdapName) {
 256                 LdapName ln = (LdapName)n;
 257                 rdn = (Rdn)ln.rdns.elementAt(i - beg);
 258             } else {
 259                 String rdnString = n.get(i - beg);
 260                 try {
 261                     rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn();
 262                 } catch (InvalidNameException e) {
 263                     return false;
 264                 }
 265             }
 266 
 267             if (!rdn.equals(rdns.elementAt(i))) {
 268                 return false;
 269             }
 270         }
 271         return true;
 272     }
 273 
 274     public Name addAll(Name suffix) throws InvalidNameException {
 275         return addAll(size(), suffix);
 276     }
 277 
 278     /*
 279      * If "suffix" is not an LdapName, each of its components is parsed as
 280      * the string form of an RDN.
 281      */
 282     public Name addAll(int pos, Name suffix) throws InvalidNameException {
 283         if (suffix instanceof LdapName) {
 284             LdapName s = (LdapName)suffix;
 285             for (int i = 0; i < s.rdns.size(); i++) {
 286                 rdns.insertElementAt(s.rdns.elementAt(i), pos++);
 287             }
 288         } else {
 289             Enumeration comps = suffix.getAll();
 290             while (comps.hasMoreElements()) {
 291                 DnParser p = new DnParser((String)comps.nextElement(),
 292                     valuesCaseSensitive);
 293                 rdns.insertElementAt(p.getRdn(), pos++);
 294             }
 295         }
 296         unparsed = null;                                // no longer valid
 297         return this;
 298     }
 299 
 300     public Name add(String comp) throws InvalidNameException {
 301         return add(size(), comp);
 302     }
 303 
 304     public Name add(int pos, String comp) throws InvalidNameException {
 305         Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn();
 306         rdns.insertElementAt(rdn, pos);
 307         unparsed = null;                                // no longer valid
 308         return this;
 309     }
 310 
 311     public Object remove(int pos) throws InvalidNameException {
 312         String comp = get(pos);
 313         rdns.removeElementAt(pos);
 314         unparsed = null;                                // no longer valid
 315         return comp;
 316     }
 317 
 318 
 319     private void parse() throws InvalidNameException {
 320         rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn();
 321     }
 322 
 323     /*
 324      * Best guess as to what RFC 2253 means by "whitespace".
 325      */
 326     private static boolean isWhitespace(char c) {
 327         return (c == ' ' || c == '\r');
 328     }
 329 
 330     /**
 331      * Given the value of an attribute, returns a string suitable
 332      * for inclusion in a DN.  If the value is a string, this is
 333      * accomplished by using backslash (\) to escape the following
 334      * characters:
 335      *<ul>
 336      *<li>leading and trailing whitespace
 337      *<li><pre>, = + < > # ; " \</pre>
 338      *</ul>
 339      * If the value is a byte array, it is converted to hex
 340      * notation (such as "#CEB1DF80").
 341      */
 342     public static String escapeAttributeValue(Object val) {
 343         return TypeAndValue.escapeValue(val);
 344     }
 345 
 346     /**
 347      * Given an attribute value formated according to RFC 2253,
 348      * returns the unformated value.  Returns a string value as
 349      * a string, and a binary value as a byte array.
 350      */
 351     public static Object unescapeAttributeValue(String val) {
 352         return TypeAndValue.unescapeValue(val);
 353     }
 354 
 355     /**
 356      * Serializes only the unparsed DN, for compactness and to avoid
 357      * any implementation dependency.
 358      *
 359      * @serialdata      The DN string and a boolean indicating whether
 360      * the values are case sensitive.
 361      */
 362     private void writeObject(java.io.ObjectOutputStream s)
 363             throws java.io.IOException {
 364         s.writeObject(toString());
 365         s.writeBoolean(valuesCaseSensitive);
 366     }
 367 
 368     private void readObject(java.io.ObjectInputStream s)
 369             throws java.io.IOException, ClassNotFoundException {
 370         unparsed = (String)s.readObject();
 371         valuesCaseSensitive = s.readBoolean();
 372         try {
 373             parse();
 374         } catch (InvalidNameException e) {
 375             // shouldn't happen
 376             throw new java.io.StreamCorruptedException(
 377                     "Invalid name: " + unparsed);
 378         }
 379     }
 380 
 381     static final long serialVersionUID = -1595520034788997356L;
 382 
 383 
 384     /*
 385      * DnParser implements a recursive descent parser for a single DN.
 386      */
 387     static class DnParser {
 388 
 389         private final String name;      // DN being parsed
 390         private final char[] chars;     // characters in LDAP name being parsed
 391         private final int len;          // length of "chars"
 392         private int cur = 0;            // index of first unconsumed char in "chars"
 393         private boolean valuesCaseSensitive;
 394 
 395         /*
 396          * Given an LDAP DN in string form, returns a parser for it.
 397          */
 398         DnParser(String name, boolean valuesCaseSensitive)
 399             throws InvalidNameException {
 400             this.name = name;
 401             len = name.length();
 402             chars = name.toCharArray();
 403             this.valuesCaseSensitive = valuesCaseSensitive;
 404         }
 405 
 406         /*
 407          * Parses the DN, returning a Vector of its RDNs.
 408          */
 409         Vector getDn() throws InvalidNameException {
 410             cur = 0;
 411             Vector rdns = new Vector(len / 3 + 10);  // leave room for growth
 412 
 413             if (len == 0) {
 414                 return rdns;
 415             }
 416 
 417             rdns.addElement(parseRdn());
 418             while (cur < len) {
 419                 if (chars[cur] == ',' || chars[cur] == ';') {
 420                     ++cur;
 421                     rdns.insertElementAt(parseRdn(), 0);
 422                 } else {
 423                     throw new InvalidNameException("Invalid name: " + name);
 424                 }
 425             }
 426             return rdns;
 427         }
 428 
 429         /*
 430          * Parses the DN, if it is known to contain a single RDN.
 431          */
 432         Rdn getRdn() throws InvalidNameException {
 433             Rdn rdn = parseRdn();
 434             if (cur < len) {
 435                 throw new InvalidNameException("Invalid RDN: " + name);
 436             }
 437             return rdn;
 438         }
 439 
 440         /*
 441          * Parses the next RDN and returns it.  Throws an exception if
 442          * none is found.  Leading and trailing whitespace is consumed.
 443          */
 444         private Rdn parseRdn() throws InvalidNameException {
 445 
 446             Rdn rdn = new Rdn();
 447             while (cur < len) {
 448                 consumeWhitespace();
 449                 String attrType = parseAttrType();
 450                 consumeWhitespace();
 451                 if (cur >= len || chars[cur] != '=') {
 452                     throw new InvalidNameException("Invalid name: " + name);
 453                 }
 454                 ++cur;          // consume '='
 455                 consumeWhitespace();
 456                 String value = parseAttrValue();
 457                 consumeWhitespace();
 458 
 459                 rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive));
 460                 if (cur >= len || chars[cur] != '+') {
 461                     break;
 462                 }
 463                 ++cur;          // consume '+'
 464             }
 465             return rdn;
 466         }
 467 
 468         /*
 469          * Returns the attribute type that begins at the next unconsumed
 470          * char.  No leading whitespace is expected.
 471          * This routine is more generous than RFC 2253.  It accepts
 472          * attribute types composed of any nonempty combination of Unicode
 473          * letters, Unicode digits, '.', '-', and internal space characters.
 474          */
 475         private String parseAttrType() throws InvalidNameException {
 476 
 477             final int beg = cur;
 478             while (cur < len) {
 479                 char c = chars[cur];
 480                 if (Character.isLetterOrDigit(c) ||
 481                       c == '.' ||
 482                       c == '-' ||
 483                       c == ' ') {
 484                     ++cur;
 485                 } else {
 486                     break;
 487                 }
 488             }
 489             // Back out any trailing spaces.
 490             while ((cur > beg) && (chars[cur - 1] == ' ')) {
 491                 --cur;
 492             }
 493 
 494             if (beg == cur) {
 495                 throw new InvalidNameException("Invalid name: " + name);
 496             }
 497             return new String(chars, beg, cur - beg);
 498         }
 499 
 500         /*
 501          * Returns the attribute value that begins at the next unconsumed
 502          * char.  No leading whitespace is expected.
 503          */
 504         private String parseAttrValue() throws InvalidNameException {
 505 
 506             if (cur < len && chars[cur] == '#') {
 507                 return parseBinaryAttrValue();
 508             } else if (cur < len && chars[cur] == '"') {
 509                 return parseQuotedAttrValue();
 510             } else {
 511                 return parseStringAttrValue();
 512             }
 513         }
 514 
 515         private String parseBinaryAttrValue() throws InvalidNameException {
 516             final int beg = cur;
 517             ++cur;                      // consume '#'
 518             while (cur < len &&
 519                    Character.isLetterOrDigit(chars[cur])) {
 520                 ++cur;
 521             }
 522             return new String(chars, beg, cur - beg);
 523         }
 524 
 525         private String parseQuotedAttrValue() throws InvalidNameException {
 526 
 527             final int beg = cur;
 528             ++cur;                      // consume '"'
 529 
 530             while ((cur < len) && chars[cur] != '"') {
 531                 if (chars[cur] == '\\') {
 532                     ++cur;              // consume backslash, then what follows
 533                 }
 534                 ++cur;
 535             }
 536             if (cur >= len) {   // no closing quote
 537                 throw new InvalidNameException("Invalid name: " + name);
 538             }
 539             ++cur       ;       // consume closing quote
 540 
 541             return new String(chars, beg, cur - beg);
 542         }
 543 
 544         private String parseStringAttrValue() throws InvalidNameException {
 545 
 546             final int beg = cur;
 547             int esc = -1;       // index of the most recently escaped character
 548 
 549             while ((cur < len) && !atTerminator()) {
 550                 if (chars[cur] == '\\') {
 551                     ++cur;              // consume backslash, then what follows
 552                     esc = cur;
 553                 }
 554                 ++cur;
 555             }
 556             if (cur > len) {            // 'twas backslash followed by nothing
 557                 throw new InvalidNameException("Invalid name: " + name);
 558             }
 559 
 560             // Trim off (unescaped) trailing whitespace.
 561             int end;
 562             for (end = cur; end > beg; end--) {
 563                 if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) {
 564                     break;
 565                 }
 566             }
 567             return new String(chars, beg, end - beg);
 568         }
 569 
 570         private void consumeWhitespace() {
 571             while ((cur < len) && isWhitespace(chars[cur])) {
 572                 ++cur;
 573             }
 574         }
 575 
 576         /*
 577          * Returns true if next unconsumed character is one that terminates
 578          * a string attribute value.
 579          */
 580         private boolean atTerminator() {
 581             return (cur < len &&
 582                     (chars[cur] == ',' ||
 583                      chars[cur] == ';' ||
 584                      chars[cur] == '+'));
 585         }
 586     }
 587 
 588 
 589     /*
 590      * Class Rdn represents a set of TypeAndValue.
 591      */
 592     static class Rdn {
 593 
 594         /*
 595          * A vector of the TypeAndValue elements of this Rdn.
 596          * It is sorted to facilitate set operations.
 597          */
 598         private final Vector tvs = new Vector();
 599 
 600         void add(TypeAndValue tv) {
 601 
 602             // Set i to index of first element greater than tv, or to
 603             // tvs.size() if there is none.
 604             int i;
 605             for (i = 0; i < tvs.size(); i++) {
 606                 int diff = tv.compareTo(tvs.elementAt(i));
 607                 if (diff == 0) {
 608                     return;             // tv is a duplicate:  ignore it
 609                 } else if (diff < 0) {
 610                     break;
 611                 }
 612             }
 613 
 614             tvs.insertElementAt(tv, i);
 615         }
 616 
 617         public String toString() {
 618             StringBuffer buf = new StringBuffer();
 619             for (int i = 0; i < tvs.size(); i++) {
 620                 if (i > 0) {
 621                     buf.append('+');
 622                 }
 623                 buf.append(tvs.elementAt(i));
 624             }
 625             return new String(buf);
 626         }
 627 
 628         public boolean equals(Object obj) {
 629             return ((obj instanceof Rdn) &&
 630                     (compareTo(obj) == 0));
 631         }
 632 
 633         // Compare TypeAndValue components one by one, lexicographically.
 634         public int compareTo(Object obj) {
 635             Rdn that = (Rdn)obj;
 636             int minSize = Math.min(tvs.size(), that.tvs.size());
 637             for (int i = 0; i < minSize; i++) {
 638                 // Compare a single pair of type/value pairs.
 639                 TypeAndValue tv = (TypeAndValue)tvs.elementAt(i);
 640                 int diff = tv.compareTo(that.tvs.elementAt(i));
 641                 if (diff != 0) {
 642                     return diff;
 643                 }
 644             }
 645             return (tvs.size() - that.tvs.size());      // longer RDN wins
 646         }
 647 
 648         public int hashCode() {
 649             // Sum up the hash codes of the components.
 650             int hash = 0;
 651 
 652             // For each type/value pair...
 653             for (int i = 0; i < tvs.size(); i++) {
 654                 hash += tvs.elementAt(i).hashCode();
 655             }
 656             return hash;
 657         }
 658 
 659         Attributes toAttributes() {
 660             Attributes attrs = new BasicAttributes(true);
 661             TypeAndValue tv;
 662             Attribute attr;
 663 
 664             for (int i = 0; i < tvs.size(); i++) {
 665                 tv = (TypeAndValue) tvs.elementAt(i);
 666                 if ((attr = attrs.get(tv.getType())) == null) {
 667                     attrs.put(tv.getType(), tv.getUnescapedValue());
 668                 } else {
 669                     attr.add(tv.getUnescapedValue());
 670                 }
 671             }
 672             return attrs;
 673         }
 674     }
 675 
 676 
 677     /*
 678      * Class TypeAndValue represents an attribute type and its
 679      * corresponding value.
 680      */
 681     static class TypeAndValue {
 682 
 683         private final String type;
 684         private final String value;             // value, escaped or quoted
 685         private final boolean binary;
 686         private final boolean valueCaseSensitive;
 687 
 688         // If non-null, a canonical represention of the value suitable
 689         // for comparison using String.compareTo().
 690         private String comparable = null;
 691 
 692         TypeAndValue(String type, String value, boolean valueCaseSensitive) {
 693             this.type = type;
 694             this.value = value;
 695             binary = value.startsWith("#");
 696             this.valueCaseSensitive = valueCaseSensitive;
 697         }
 698 
 699         public String toString() {
 700             return (type + "=" + value);
 701         }
 702 
 703         public int compareTo(Object obj) {
 704             // NB: Any change here affecting equality must be
 705             //     reflected in hashCode().
 706 
 707             TypeAndValue that = (TypeAndValue)obj;
 708 
 709             int diff = type.toUpperCase().compareTo(that.type.toUpperCase());
 710             if (diff != 0) {
 711                 return diff;
 712             }
 713             if (value.equals(that.value)) {     // try shortcut
 714                 return 0;
 715             }
 716             return getValueComparable().compareTo(that.getValueComparable());
 717         }
 718 
 719         public boolean equals(Object obj) {
 720             // NB:  Any change here must be reflected in hashCode().
 721             if (!(obj instanceof TypeAndValue)) {
 722                 return false;
 723             }
 724             TypeAndValue that = (TypeAndValue)obj;
 725             return (type.equalsIgnoreCase(that.type) &&
 726                     (value.equals(that.value) ||
 727                      getValueComparable().equals(that.getValueComparable())));
 728         }
 729 
 730         public int hashCode() {
 731             // If two objects are equal, their hash codes must match.
 732             return (type.toUpperCase().hashCode() +
 733                     getValueComparable().hashCode());
 734         }
 735 
 736         /*
 737          * Returns the type.
 738          */
 739         String getType() {
 740             return type;
 741         }
 742 
 743         /*
 744          * Returns the unescaped value.
 745          */
 746         Object getUnescapedValue() {
 747             return unescapeValue(value);
 748         }
 749 
 750         /*
 751          * Returns a canonical representation of "value" suitable for
 752          * comparison using String.compareTo().  If "value" is a string,
 753          * it is returned with escapes and quotes stripped away, and
 754          * hex-encoded UTF-8 converted to 16-bit Unicode chars.
 755          * If value's case is to be ignored, it is returned in uppercase.
 756          * If "value" is binary, it is returned in uppercase but
 757          * otherwise unmodified.
 758          */
 759         private String getValueComparable() {
 760             if (comparable != null) {
 761                 return comparable;      // return cached result
 762             }
 763 
 764             // cache result
 765             if (binary) {
 766                 comparable = value.toUpperCase();
 767             } else {
 768                 comparable = (String)unescapeValue(value);
 769                 if (!valueCaseSensitive) {
 770                     comparable = comparable.toUpperCase(); // ignore case
 771                 }
 772             }
 773             return comparable;
 774         }
 775 
 776         /*
 777          * Given the value of an attribute, returns a string suitable
 778          * for inclusion in a DN.
 779          */
 780         static String escapeValue(Object val) {
 781             return (val instanceof byte[])
 782                 ? escapeBinaryValue((byte[])val)
 783                 : escapeStringValue((String)val);
 784         }
 785 
 786         /*
 787          * Given the value of a string-valued attribute, returns a
 788          * string suitable for inclusion in a DN.  This is accomplished by
 789          * using backslash (\) to escape the following characters:
 790          *      leading and trailing whitespace
 791          *      , = + < > # ; " \
 792          */
 793         private static String escapeStringValue(String val) {
 794 
 795             final String escapees = ",=+<>#;\"\\";
 796             char[] chars = val.toCharArray();
 797             StringBuffer buf = new StringBuffer(2 * val.length());
 798 
 799             // Find leading and trailing whitespace.
 800             int lead;   // index of first char that is not leading whitespace
 801             for (lead = 0; lead < chars.length; lead++) {
 802                 if (!isWhitespace(chars[lead])) {
 803                     break;
 804                 }
 805             }
 806             int trail;  // index of last char that is not trailing whitespace
 807             for (trail = chars.length - 1; trail >= 0; trail--) {
 808                 if (!isWhitespace(chars[trail])) {
 809                     break;
 810                 }
 811             }
 812 
 813             for (int i = 0; i < chars.length; i++) {
 814                 char c = chars[i];
 815                 if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {
 816                     buf.append('\\');
 817                 }
 818                 buf.append(c);
 819             }
 820             return new String(buf);
 821         }
 822 
 823         /*
 824          * Given the value of a binary attribute, returns a string
 825          * suitable for inclusion in a DN (such as "#CEB1DF80").
 826          */
 827         private static String escapeBinaryValue(byte[] val) {
 828 
 829             StringBuffer buf = new StringBuffer(1 + 2 * val.length);
 830             buf.append("#");
 831 
 832             for (int i = 0; i < val.length; i++) {
 833                 byte b = val[i];
 834                 buf.append(Character.forDigit(0xF & (b >>> 4), 16));
 835                 buf.append(Character.forDigit(0xF & b, 16));
 836             }
 837 
 838             return (new String(buf)).toUpperCase();
 839         }
 840 
 841         /*
 842          * Given an attribute value formated according to RFC 2253,
 843          * returns the unformated value.  Escapes and quotes are
 844          * stripped away, and hex-encoded UTF-8 is converted to 16-bit
 845          * Unicode chars.  Returns a string value as a String, and a
 846          * binary value as a byte array.
 847          */
 848         static Object unescapeValue(String val) {
 849 
 850             char[] chars = val.toCharArray();
 851             int beg = 0;
 852             int end = chars.length;
 853 
 854             // Trim off leading and trailing whitespace.
 855             while ((beg < end) && isWhitespace(chars[beg])) {
 856                 ++beg;
 857             }
 858             while ((beg < end) && isWhitespace(chars[end - 1])) {
 859                 --end;
 860             }
 861 
 862             // Add back the trailing whitespace with a preceeding '\'
 863             // (escaped or unescaped) that was taken off in the above
 864             // loop. Whether or not to retain this whitespace is
 865             // decided below.
 866             if (end != chars.length &&
 867                     (beg < end) &&
 868                     chars[end - 1] == '\\') {
 869                 end++;
 870             }
 871             if (beg >= end) {
 872                 return "";
 873             }
 874 
 875             if (chars[beg] == '#') {
 876                 // Value is binary (eg: "#CEB1DF80").
 877                 return decodeHexPairs(chars, ++beg, end);
 878             }
 879 
 880             // Trim off quotes.
 881             if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
 882                 ++beg;
 883                 --end;
 884             }
 885 
 886             StringBuffer buf = new StringBuffer(end - beg);
 887             int esc = -1; // index of the last escaped character
 888 
 889             for (int i = beg; i < end; i++) {
 890                 if ((chars[i] == '\\') && (i + 1 < end)) {
 891                     if (!Character.isLetterOrDigit(chars[i + 1])) {
 892                         ++i;                    // skip backslash
 893                         buf.append(chars[i]);   // snarf escaped char
 894                         esc = i;
 895                     } else {
 896 
 897                         // Convert hex-encoded UTF-8 to 16-bit chars.
 898                         byte[] utf8 = getUtf8Octets(chars, i, end);
 899                         if (utf8.length > 0) {
 900                             try {
 901                                 buf.append(new String(utf8, "UTF8"));
 902                             } catch (java.io.UnsupportedEncodingException e) {
 903                                 // shouldn't happen
 904                             }
 905                             i += utf8.length * 3 - 1;
 906                         } else {
 907                             throw new IllegalArgumentException(
 908                                 "Not a valid attribute string value:" +
 909                                 val +", improper usage of backslash");
 910                         }
 911                     }
 912                 } else {
 913                     buf.append(chars[i]);       // snarf unescaped char
 914                 }
 915             }
 916 
 917             // Get rid of the unescaped trailing whitespace with the
 918             // preceeding '\' character that was previously added back.
 919             int len = buf.length();
 920             if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) {
 921                 buf.setLength(len - 1);
 922             }
 923 
 924             return new String(buf);
 925         }
 926 
 927 
 928         /*
 929          * Given an array of chars (with starting and ending indexes into it)
 930          * representing bytes encoded as hex-pairs (such as "CEB1DF80"),
 931          * returns a byte array containing the decoded bytes.
 932          */
 933         private static byte[] decodeHexPairs(char[] chars, int beg, int end) {
 934             byte[] bytes = new byte[(end - beg) / 2];
 935             for (int i = 0; beg + 1 < end; i++) {
 936                 int hi = Character.digit(chars[beg], 16);
 937                 int lo = Character.digit(chars[beg + 1], 16);
 938                 if (hi < 0 || lo < 0) {
 939                     break;
 940                 }
 941                 bytes[i] = (byte)((hi<<4) + lo);
 942                 beg += 2;
 943             }
 944             if (beg != end) {
 945                 throw new IllegalArgumentException(
 946                         "Illegal attribute value: #" + new String(chars));
 947             }
 948             return bytes;
 949         }
 950 
 951         /*
 952          * Given an array of chars (with starting and ending indexes into it),
 953          * finds the largest prefix consisting of hex-encoded UTF-8 octets,
 954          * and returns a byte array containing the corresponding UTF-8 octets.
 955          *
 956          * Hex-encoded UTF-8 octets look like this:
 957          *      \03\B1\DF\80
 958          */
 959         private static byte[] getUtf8Octets(char[] chars, int beg, int end) {
 960             byte[] utf8 = new byte[(end - beg) / 3];    // allow enough room
 961             int len = 0;        // index of first unused byte in utf8
 962 
 963             while ((beg + 2 < end) &&
 964                    (chars[beg++] == '\\')) {
 965                 int hi = Character.digit(chars[beg++], 16);
 966                 int lo = Character.digit(chars[beg++], 16);
 967                 if (hi < 0 || lo < 0) {
 968                     break;
 969                 }
 970                 utf8[len++] = (byte)((hi<<4) + lo);
 971             }
 972 
 973             if (len == utf8.length) {
 974                 return utf8;
 975             } else {
 976                 byte[] res = new byte[len];
 977                 System.arraycopy(utf8, 0, res, 0, len);
 978                 return res;
 979             }
 980         }
 981     }
 982 
 983 
 984     /*
 985      * For testing.
 986      */
 987 /*
 988     public static void main(String[] args) {
 989 
 990         try {
 991             if (args.length == 1) {             // parse and print components
 992                 LdapName n = new LdapName(args[0]);
 993 
 994                 Enumeration rdns = n.rdns.elements();
 995                 while (rdns.hasMoreElements()) {
 996                     Rdn rdn = (Rdn)rdns.nextElement();
 997                     for (int i = 0; i < rdn.tvs.size(); i++) {
 998                         System.out.print("[" + rdn.tvs.elementAt(i) + "]");
 999                     }
1000                     System.out.println();
1001                 }
1002 
1003             } else {                            // compare two names
1004                 LdapName n1 = new LdapName(args[0]);
1005                 LdapName n2 = new LdapName(args[1]);
1006                 n1.unparsed = null;
1007                 n2.unparsed = null;
1008                 boolean eq = n1.equals(n2);
1009                 System.out.println("[" + n1 + (eq ? "] == [" : "] != [")
1010                                    + n2 + "]");
1011             }
1012         } catch (Exception e) {
1013             e.printStackTrace();
1014         }
1015     }
1016 */
1017 }