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
26package com.sun.jndi.ldap;
27
28
29import java.util.Enumeration;
30import java.util.Vector;
31import java.util.Locale;
32
33import javax.naming.*;
34import javax.naming.directory.Attributes;
35import javax.naming.directory.Attribute;
36import 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
79public 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}
1021