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