1/*
2 * Copyright (c) 2003, 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 javax.naming.ldap;
27
28import java.util.Iterator;
29import java.util.NoSuchElementException;
30import java.util.ArrayList;
31import java.util.Locale;
32import java.util.Collections;
33
34import javax.naming.InvalidNameException;
35import javax.naming.directory.BasicAttributes;
36import javax.naming.directory.Attributes;
37import javax.naming.directory.Attribute;
38import javax.naming.NamingEnumeration;
39import javax.naming.NamingException;
40
41import java.io.Serializable;
42import java.io.ObjectOutputStream;
43import java.io.ObjectInputStream;
44import java.io.IOException;
45
46/**
47 * This class represents a relative distinguished name, or RDN, which is a
48 * component of a distinguished name as specified by
49 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
50 * An example of an RDN is "OU=Sales+CN=J.Smith". In this example,
51 * the RDN consist of multiple attribute type/value pairs. The
52 * RDN is parsed as described in the class description for
53 * {@link javax.naming.ldap.LdapName LdapName}.
54 * <p>
55 * The Rdn class represents an RDN as attribute type/value mappings,
56 * which can be viewed using
57 * {@link javax.naming.directory.Attributes Attributes}.
58 * In addition, it contains convenience methods that allow easy retrieval
59 * of type and value when the Rdn consist of a single type/value pair,
60 * which is how it appears in a typical usage.
61 * It also contains helper methods that allow escaping of the unformatted
62 * attribute value and unescaping of the value formatted according to the
63 * escaping syntax defined in RFC2253. For methods that take or return
64 * attribute value as an Object, the value is either a String
65 * (in unescaped form) or a byte array.
66 * <p>
67 * <code>Rdn</code> will properly parse all valid RDNs, but
68 * does not attempt to detect all possible violations when parsing
69 * invalid RDNs. It is "generous" in accepting invalid RDNs.
70 * The "validity" of a name is determined ultimately when it
71 * is supplied to an LDAP server, which may accept or
72 * reject the name based on factors such as its schema information
73 * and interoperability considerations.
74 *
75 * <p>
76 * The following code example shows how to construct an Rdn using the
77 * constructor that takes type and value as arguments:
78 * <pre>
79 *      Rdn rdn = new Rdn("cn", "Juicy, Fruit");
80 *      System.out.println(rdn.toString());
81 * </pre>
82 * The last line will print {@code cn=Juicy\, Fruit}. The
83 * {@link #unescapeValue(String) unescapeValue()} method can be
84 * used to unescape the escaped comma resulting in the original
85 * value {@code "Juicy, Fruit"}. The {@link #escapeValue(Object)
86 * escapeValue()} method adds the escape back preceding the comma.
87 * <p>
88 * This class can be instantiated by a string representation
89 * of the RDN defined in RFC 2253 as shown in the following code example:
90 * <pre>
91 *      Rdn rdn = new Rdn("cn=Juicy\\, Fruit");
92 *      System.out.println(rdn.toString());
93 * </pre>
94 * The last line will print {@code cn=Juicy\, Fruit}.
95 * <p>
96 * Concurrent multithreaded read-only access of an instance of
97 * {@code Rdn} need not be synchronized.
98 * <p>
99 * Unless otherwise noted, the behavior of passing a null argument
100 * to a constructor or method in this class will cause NullPointerException
101 * to be thrown.
102 *
103 * @since 1.5
104 */
105
106public class Rdn implements Serializable, Comparable<Object> {
107
108    private transient ArrayList<RdnEntry> entries;
109
110    // The common case.
111    private static final int DEFAULT_SIZE = 1;
112
113    private static final long serialVersionUID = -5994465067210009656L;
114
115    /**
116     * Constructs an Rdn from the given attribute set. See
117     * {@link javax.naming.directory.Attributes Attributes}.
118     * <p>
119     * The string attribute values are not interpreted as
120     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
121     * formatted RDN strings. That is, the values are used
122     * literally (not parsed) and assumed to be unescaped.
123     *
124     * @param attrSet The non-null and non-empty attributes containing
125     * type/value mappings.
126     * @throws InvalidNameException If contents of {@code attrSet} cannot
127     *          be used to construct a valid RDN.
128     */
129    public Rdn(Attributes attrSet) throws InvalidNameException {
130        if (attrSet.size() == 0) {
131            throw new InvalidNameException("Attributes cannot be empty");
132        }
133        entries = new ArrayList<>(attrSet.size());
134        NamingEnumeration<? extends Attribute> attrs = attrSet.getAll();
135        try {
136            for (int nEntries = 0; attrs.hasMore(); nEntries++) {
137                RdnEntry entry = new RdnEntry();
138                Attribute attr = attrs.next();
139                entry.type = attr.getID();
140                entry.value = attr.get();
141                entries.add(nEntries, entry);
142            }
143        } catch (NamingException e) {
144            InvalidNameException e2 = new InvalidNameException(
145                                        e.getMessage());
146            e2.initCause(e);
147            throw e2;
148        }
149        sort(); // arrange entries for comparison
150    }
151
152    /**
153     * Constructs an Rdn from the given string.
154     * This constructor takes a string formatted according to the rules
155     * defined in <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
156     * and described in the class description for
157     * {@link javax.naming.ldap.LdapName}.
158     *
159     * @param rdnString The non-null and non-empty RFC2253 formatted string.
160     * @throws InvalidNameException If a syntax error occurs during
161     *                  parsing of the rdnString.
162     */
163    public Rdn(String rdnString) throws InvalidNameException {
164        entries = new ArrayList<>(DEFAULT_SIZE);
165        (new Rfc2253Parser(rdnString)).parseRdn(this);
166    }
167
168    /**
169     * Constructs an Rdn from the given {@code rdn}.
170     * The contents of the {@code rdn} are simply copied into the newly
171     * created Rdn.
172     * @param rdn The non-null Rdn to be copied.
173     */
174    public Rdn(Rdn rdn) {
175        entries = new ArrayList<>(rdn.entries.size());
176        entries.addAll(rdn.entries);
177    }
178
179    /**
180     * Constructs an Rdn from the given attribute type and
181     * value.
182     * The string attribute values are not interpreted as
183     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
184     * formatted RDN strings. That is, the values are used
185     * literally (not parsed) and assumed to be unescaped.
186     *
187     * @param type The non-null and non-empty string attribute type.
188     * @param value The non-null and non-empty attribute value.
189     * @throws InvalidNameException If type/value cannot be used to
190     *                  construct a valid RDN.
191     * @see #toString()
192     */
193    public Rdn(String type, Object value) throws InvalidNameException {
194        if (value == null) {
195            throw new NullPointerException("Cannot set value to null");
196        }
197        if (type.equals("") || isEmptyValue(value)) {
198            throw new InvalidNameException(
199                "type or value cannot be empty, type:" + type +
200                " value:" + value);
201        }
202        entries = new ArrayList<>(DEFAULT_SIZE);
203        put(type, value);
204    }
205
206    private boolean isEmptyValue(Object val) {
207        return ((val instanceof String) && val.equals("")) ||
208        ((val instanceof byte[]) && (((byte[]) val).length == 0));
209    }
210
211    // An empty constructor used by the parser
212    Rdn() {
213        entries = new ArrayList<>(DEFAULT_SIZE);
214    }
215
216    /*
217     * Adds the given attribute type and value to this Rdn.
218     * The string attribute values are not interpreted as
219     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
220     * formatted RDN strings. That is the values are used
221     * literally (not parsed) and assumed to be unescaped.
222     *
223     * @param type The non-null and non-empty string attribute type.
224     * @param value The non-null and non-empty attribute value.
225     * @return The updated Rdn, not a new one. Cannot be null.
226     * @see #toString()
227     */
228    Rdn put(String type, Object value) {
229
230        // create new Entry
231        RdnEntry newEntry = new RdnEntry();
232        newEntry.type =  type;
233        if (value instanceof byte[]) {  // clone the byte array
234            newEntry.value = ((byte[]) value).clone();
235        } else {
236            newEntry.value = value;
237        }
238        entries.add(newEntry);
239        return this;
240    }
241
242    void sort() {
243        if (entries.size() > 1) {
244            Collections.sort(entries);
245        }
246    }
247
248    /**
249     * Retrieves one of this Rdn's value.
250     * This is a convenience method for obtaining the value,
251     * when the RDN contains a single type and value mapping,
252     * which is the common RDN usage.
253     * <p>
254     * For a multi-valued RDN, this method returns value corresponding
255     * to the type returned by {@link #getType() getType()} method.
256     *
257     * @return The non-null attribute value.
258     */
259    public Object getValue() {
260        return entries.get(0).getValue();
261    }
262
263    /**
264     * Retrieves one of this Rdn's type.
265     * This is a convenience method for obtaining the type,
266     * when the RDN contains a single type and value mapping,
267     * which is the common RDN usage.
268     * <p>
269     * For a multi-valued RDN, the type/value pairs have
270     * no specific order defined on them. In that case, this method
271     * returns type of one of the type/value pairs.
272     * The {@link #getValue() getValue()} method returns the
273     * value corresponding to the type returned by this method.
274     *
275     * @return The non-null attribute type.
276     */
277    public String getType() {
278        return entries.get(0).getType();
279    }
280
281    /**
282     * Returns this Rdn as a string represented in a format defined by
283     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> and described
284     * in the class description for {@link javax.naming.ldap.LdapName LdapName}.
285     *
286     * @return The string representation of the Rdn.
287     */
288    public String toString() {
289        StringBuilder builder = new StringBuilder();
290        int size = entries.size();
291        if (size > 0) {
292            builder.append(entries.get(0));
293        }
294        for (int next = 1; next < size; next++) {
295            builder.append('+');
296            builder.append(entries.get(next));
297        }
298        return builder.toString();
299    }
300
301    /**
302     * Compares this Rdn with the specified Object for order.
303     * Returns a negative integer, zero, or a positive integer as this
304     * Rdn is less than, equal to, or greater than the given Object.
305     * <p>
306     * If obj is null or not an instance of Rdn, ClassCastException
307     * is thrown.
308     * <p>
309     * The attribute type and value pairs of the RDNs are lined up
310     * against each other and compared lexicographically. The order of
311     * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
312     * significant.
313     *
314     * @param obj The non-null object to compare against.
315     * @return  A negative integer, zero, or a positive integer as this Rdn
316     *          is less than, equal to, or greater than the given Object.
317     * @exception ClassCastException if obj is null or not a Rdn.
318     */
319    public int compareTo(Object obj) {
320        if (!(obj instanceof Rdn)) {
321            throw new ClassCastException("The obj is not a Rdn");
322        }
323        if (obj == this) {
324            return 0;
325        }
326        Rdn that = (Rdn) obj;
327        int minSize = Math.min(entries.size(), that.entries.size());
328        for (int i = 0; i < minSize; i++) {
329
330            // Compare a single pair of type/value pairs.
331            int diff = entries.get(i).compareTo(that.entries.get(i));
332            if (diff != 0) {
333                return diff;
334            }
335        }
336        return (entries.size() - that.entries.size());  // longer RDN wins
337    }
338
339    /**
340     * Compares the specified Object with this Rdn for equality.
341     * Returns true if the given object is also a Rdn and the two Rdns
342     * represent the same attribute type and value mappings. The order of
343     * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
344     * significant.
345     * <p>
346     * Type and value equality matching is done as below:
347     * <ul>
348     * <li> The types are compared for equality with their case ignored.
349     * <li> String values with different but equivalent usage of quoting,
350     * escaping, or UTF8-hex-encoding are considered equal.
351     * The case of the values is ignored during the comparison.
352     * </ul>
353     * <p>
354     * If obj is null or not an instance of Rdn, false is returned.
355     *
356     * @param obj object to be compared for equality with this Rdn.
357     * @return true if the specified object is equal to this Rdn.
358     * @see #hashCode()
359     */
360    public boolean equals(Object obj) {
361        if (obj == this) {
362            return true;
363        }
364        if (!(obj instanceof Rdn)) {
365            return false;
366        }
367        Rdn that = (Rdn) obj;
368        if (entries.size() != that.size()) {
369            return false;
370        }
371        for (int i = 0; i < entries.size(); i++) {
372            if (!entries.get(i).equals(that.entries.get(i))) {
373                return false;
374            }
375        }
376        return true;
377    }
378
379    /**
380     * Returns the hash code of this RDN. Two RDNs that are
381     * equal (according to the equals method) will have the same
382     * hash code.
383     *
384     * @return An int representing the hash code of this Rdn.
385     * @see #equals
386     */
387    public int hashCode() {
388
389        // Sum up the hash codes of the components.
390        int hash = 0;
391
392        // For each type/value pair...
393        for (int i = 0; i < entries.size(); i++) {
394            hash += entries.get(i).hashCode();
395        }
396        return hash;
397    }
398
399    /**
400     * Retrieves the {@link javax.naming.directory.Attributes Attributes}
401     * view of the type/value mappings contained in this Rdn.
402     *
403     * @return  The non-null attributes containing the type/value
404     *          mappings of this Rdn.
405     */
406    public Attributes toAttributes() {
407        Attributes attrs = new BasicAttributes(true);
408        for (int i = 0; i < entries.size(); i++) {
409            RdnEntry entry = entries.get(i);
410            Attribute attr = attrs.put(entry.getType(), entry.getValue());
411            if (attr != null) {
412                attr.add(entry.getValue());
413                attrs.put(attr);
414            }
415        }
416        return attrs;
417    }
418
419
420    private static class RdnEntry implements Comparable<RdnEntry> {
421        private String type;
422        private Object value;
423
424        // If non-null, a canonical representation of the value suitable
425        // for comparison using String.compareTo()
426        private String comparable = null;
427
428        String getType() {
429            return type;
430        }
431
432        Object getValue() {
433            return value;
434        }
435
436        public int compareTo(RdnEntry that) {
437            int diff = type.compareToIgnoreCase(that.type);
438            if (diff != 0) {
439                return diff;
440            }
441            if (value.equals(that.value)) {     // try shortcut
442                return 0;
443            }
444            return getValueComparable().compareTo(
445                        that.getValueComparable());
446        }
447
448        public boolean equals(Object obj) {
449            if (obj == this) {
450                return true;
451            }
452            if (!(obj instanceof RdnEntry)) {
453                return false;
454            }
455
456            // Any change here must be reflected in hashCode()
457            RdnEntry that = (RdnEntry) obj;
458            return (type.equalsIgnoreCase(that.type)) &&
459                        (getValueComparable().equals(
460                        that.getValueComparable()));
461        }
462
463        public int hashCode() {
464            return (type.toUpperCase(Locale.ENGLISH).hashCode() +
465                getValueComparable().hashCode());
466        }
467
468        public String toString() {
469            return type + "=" + escapeValue(value);
470        }
471
472        private String getValueComparable() {
473            if (comparable != null) {
474                return comparable;              // return cached result
475            }
476
477            // cache result
478            if (value instanceof byte[]) {
479                comparable = escapeBinaryValue((byte[]) value);
480            } else {
481                comparable = ((String) value).toUpperCase(Locale.ENGLISH);
482            }
483            return comparable;
484        }
485    }
486
487    /**
488     * Retrieves the number of attribute type/value pairs in this Rdn.
489     * @return The non-negative number of type/value pairs in this Rdn.
490     */
491    public int size() {
492        return entries.size();
493    }
494
495    /**
496     * Given the value of an attribute, returns a string escaped according
497     * to the rules specified in
498     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
499     * <p>
500     * For example, if the val is "Sue, Grabbit and Runn", the escaped
501     * value returned by this method is "Sue\, Grabbit and Runn".
502     * <p>
503     * A string value is represented as a String and binary value
504     * as a byte array.
505     *
506     * @param val The non-null object to be escaped.
507     * @return Escaped string value.
508     * @throws ClassCastException if val is not a String or byte array.
509     */
510    public static String escapeValue(Object val) {
511        return (val instanceof byte[])
512                ? escapeBinaryValue((byte[])val)
513                : escapeStringValue((String)val);
514    }
515
516    /*
517     * Given the value of a string-valued attribute, returns a
518     * string suitable for inclusion in a DN.  This is accomplished by
519     * using backslash (\) to escape the following characters:
520     *  leading and trailing whitespace
521     *  , = + < > # ; " \
522     */
523    private static final String escapees = ",=+<>#;\"\\";
524
525    private static String escapeStringValue(String val) {
526
527            char[] chars = val.toCharArray();
528            StringBuilder builder = new StringBuilder(2 * val.length());
529
530            // Find leading and trailing whitespace.
531            int lead;   // index of first char that is not leading whitespace
532            for (lead = 0; lead < chars.length; lead++) {
533                if (!isWhitespace(chars[lead])) {
534                    break;
535                }
536            }
537            int trail;  // index of last char that is not trailing whitespace
538            for (trail = chars.length - 1; trail >= 0; trail--) {
539                if (!isWhitespace(chars[trail])) {
540                    break;
541                }
542            }
543
544            for (int i = 0; i < chars.length; i++) {
545                char c = chars[i];
546                if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {
547                    builder.append('\\');
548                }
549                builder.append(c);
550            }
551            return builder.toString();
552    }
553
554    /*
555     * Given the value of a binary attribute, returns a string
556     * suitable for inclusion in a DN (such as "#CEB1DF80").
557     * TBD: This method should actually generate the ber encoding
558     * of the binary value
559     */
560    private static String escapeBinaryValue(byte[] val) {
561
562        StringBuilder builder = new StringBuilder(1 + 2 * val.length);
563        builder.append("#");
564
565        for (int i = 0; i < val.length; i++) {
566            byte b = val[i];
567            builder.append(Character.forDigit(0xF & (b >>> 4), 16));
568            builder.append(Character.forDigit(0xF & b, 16));
569        }
570        return builder.toString();
571    }
572
573    /**
574     * Given an attribute value string formatted according to the rules
575     * specified in
576     * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>,
577     * returns the unformatted value.  Escapes and quotes are
578     * stripped away, and hex-encoded UTF-8 is converted to equivalent
579     * UTF-16 characters. Returns a string value as a String, and a
580     * binary value as a byte array.
581     * <p>
582     * Legal and illegal values are defined in RFC 2253.
583     * This method is generous in accepting the values and does not
584     * catch all illegal values.
585     * Therefore, passing in an illegal value might not necessarily
586     * trigger an {@code IllegalArgumentException}.
587     *
588     * @param   val     The non-null string to be unescaped.
589     * @return          Unescaped value.
590     * @throws          IllegalArgumentException When an Illegal value
591     *                  is provided.
592     */
593    public static Object unescapeValue(String val) {
594
595            char[] chars = val.toCharArray();
596            int beg = 0;
597            int end = chars.length;
598
599            // Trim off leading and trailing whitespace.
600            while ((beg < end) && isWhitespace(chars[beg])) {
601                ++beg;
602            }
603
604            while ((beg < end) && isWhitespace(chars[end - 1])) {
605                --end;
606            }
607
608            // Add back the trailing whitespace with a preceding '\'
609            // (escaped or unescaped) that was taken off in the above
610            // loop. Whether or not to retain this whitespace is decided below.
611            if (end != chars.length &&
612                    (beg < end) &&
613                    chars[end - 1] == '\\') {
614                end++;
615            }
616            if (beg >= end) {
617                return "";
618            }
619
620            if (chars[beg] == '#') {
621                // Value is binary (eg: "#CEB1DF80").
622                return decodeHexPairs(chars, ++beg, end);
623            }
624
625            // Trim off quotes.
626            if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
627                ++beg;
628                --end;
629            }
630
631            StringBuilder builder = new StringBuilder(end - beg);
632            int esc = -1; // index of the last escaped character
633
634            for (int i = beg; i < end; i++) {
635                if ((chars[i] == '\\') && (i + 1 < end)) {
636                    if (!Character.isLetterOrDigit(chars[i + 1])) {
637                        ++i;                            // skip backslash
638                        builder.append(chars[i]);       // snarf escaped char
639                        esc = i;
640                    } else {
641
642                        // Convert hex-encoded UTF-8 to 16-bit chars.
643                        byte[] utf8 = getUtf8Octets(chars, i, end);
644                        if (utf8.length > 0) {
645                            try {
646                                builder.append(new String(utf8, "UTF8"));
647                            } catch (java.io.UnsupportedEncodingException e) {
648                                // shouldn't happen
649                            }
650                            i += utf8.length * 3 - 1;
651                        } else { // no utf8 bytes available, invalid DN
652
653                            // '/' has no meaning, throw exception
654                            throw new IllegalArgumentException(
655                                "Not a valid attribute string value:" +
656                                val + ",improper usage of backslash");
657                        }
658                    }
659                } else {
660                    builder.append(chars[i]);   // snarf unescaped char
661                }
662            }
663
664            // Get rid of the unescaped trailing whitespace with the
665            // preceding '\' character that was previously added back.
666            int len = builder.length();
667            if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) {
668                builder.setLength(len - 1);
669            }
670            return builder.toString();
671        }
672
673
674        /*
675         * Given an array of chars (with starting and ending indexes into it)
676         * representing bytes encoded as hex-pairs (such as "CEB1DF80"),
677         * returns a byte array containing the decoded bytes.
678         */
679        private static byte[] decodeHexPairs(char[] chars, int beg, int end) {
680            byte[] bytes = new byte[(end - beg) / 2];
681            for (int i = 0; beg + 1 < end; i++) {
682                int hi = Character.digit(chars[beg], 16);
683                int lo = Character.digit(chars[beg + 1], 16);
684                if (hi < 0 || lo < 0) {
685                    break;
686                }
687                bytes[i] = (byte)((hi<<4) + lo);
688                beg += 2;
689            }
690            if (beg != end) {
691                throw new IllegalArgumentException(
692                        "Illegal attribute value: " + new String(chars));
693            }
694            return bytes;
695        }
696
697        /*
698         * Given an array of chars (with starting and ending indexes into it),
699         * finds the largest prefix consisting of hex-encoded UTF-8 octets,
700         * and returns a byte array containing the corresponding UTF-8 octets.
701         *
702         * Hex-encoded UTF-8 octets look like this:
703         *      \03\B1\DF\80
704         */
705        private static byte[] getUtf8Octets(char[] chars, int beg, int end) {
706            byte[] utf8 = new byte[(end - beg) / 3];    // allow enough room
707            int len = 0;        // index of first unused byte in utf8
708
709            while ((beg + 2 < end) &&
710                   (chars[beg++] == '\\')) {
711                int hi = Character.digit(chars[beg++], 16);
712                int lo = Character.digit(chars[beg++], 16);
713                if (hi < 0 || lo < 0) {
714                   break;
715                }
716                utf8[len++] = (byte)((hi<<4) + lo);
717            }
718            if (len == utf8.length) {
719                return utf8;
720            } else {
721                byte[] res = new byte[len];
722                System.arraycopy(utf8, 0, res, 0, len);
723                return res;
724            }
725        }
726
727    /*
728     * Best guess as to what RFC 2253 means by "whitespace".
729     */
730    private static boolean isWhitespace(char c) {
731        return (c == ' ' || c == '\r');
732    }
733
734    /**
735     * Serializes only the unparsed RDN, for compactness and to avoid
736     * any implementation dependency.
737     *
738     * @serialData      The RDN string
739     */
740    private void writeObject(ObjectOutputStream s)
741            throws java.io.IOException {
742        s.defaultWriteObject();
743        s.writeObject(toString());
744    }
745
746    private void readObject(ObjectInputStream s)
747            throws IOException, ClassNotFoundException {
748        s.defaultReadObject();
749        entries = new ArrayList<>(DEFAULT_SIZE);
750        String unparsed = (String) s.readObject();
751        try {
752            (new Rfc2253Parser(unparsed)).parseRdn(this);
753        } catch (InvalidNameException e) {
754            // shouldn't happen
755            throw new java.io.StreamCorruptedException(
756                    "Invalid name: " + unparsed);
757        }
758    }
759}
760