001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS
026 */
027package org.opends.server.types;
028
029import static org.forgerock.util.Reject.*;
030import static org.opends.messages.SchemaMessages.*;
031import static org.opends.server.config.ConfigConstants.*;
032import static org.opends.server.util.StaticUtils.*;
033
034import java.io.Serializable;
035import java.util.LinkedList;
036import java.util.List;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.ldap.ByteSequence;
041import org.forgerock.opendj.ldap.ByteSequenceReader;
042import org.forgerock.opendj.ldap.ByteString;
043import org.forgerock.opendj.ldap.ByteStringBuilder;
044import org.forgerock.opendj.ldap.ResultCode;
045import org.forgerock.opendj.ldap.SearchScope;
046import org.forgerock.util.Reject;
047import org.opends.server.core.DirectoryServer;
048
049/**
050 * This class defines a data structure for storing and interacting
051 * with the distinguished names associated with entries in the
052 * Directory Server.
053 */
054@org.opends.server.types.PublicAPI(
055     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
056     mayInstantiate=true,
057     mayExtend=false,
058     mayInvoke=true)
059public final class DN implements Comparable<DN>, Serializable
060{
061  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
062
063  /**
064   * A singleton instance of the null DN (a DN with no components).
065   */
066  public static final DN NULL_DN = new DN();
067
068  /** RDN separator for normalized byte string of a DN. */
069  public static final byte NORMALIZED_RDN_SEPARATOR = 0x00;
070
071  /** AVA separator for normalized byte string of a DN. */
072  public static final byte NORMALIZED_AVA_SEPARATOR = 0x01;
073
074  /** Escape byte for normalized byte string of a DN. */
075  public static final byte NORMALIZED_ESC_BYTE = 0x02;
076
077  /**
078   * The serial version identifier required to satisfy the compiler
079   * because this class implements the
080   * <CODE>java.io.Serializable</CODE> interface.  This value was
081   * generated using the <CODE>serialver</CODE> command-line utility
082   * included with the Java SDK.
083   */
084  private static final long serialVersionUID = 1184263456768819888L;
085
086  /** The number of RDN components that comprise this DN. */
087  private final int numComponents;
088
089  /**
090   * The set of RDN components that comprise this DN, arranged with the suffix
091   * as the last element.
092   */
093  private final RDN[] rdnComponents;
094
095  /** The string representation of this DN. */
096  private String dnString;
097
098  /**
099   * The normalized byte string representation of this DN, which is not
100   * a valid DN and is not reversible to a valid DN.
101   */
102  private ByteString normalizedDN;
103
104  /**
105   * Creates a new DN with no RDN components (i.e., a null DN or root
106   * DSE).
107   */
108  public DN()
109  {
110    this(new RDN[0]);
111  }
112
113  /**
114   * Creates a new DN with the provided set of RDNs, arranged with the
115   * suffix as the last element.
116   *
117   * @param  rdnComponents  The set of RDN components that make up
118   *                        this DN.
119   */
120  public DN(RDN[] rdnComponents)
121  {
122    if (rdnComponents == null)
123    {
124      this.rdnComponents = new RDN[0];
125    }
126    else
127    {
128      this.rdnComponents = rdnComponents;
129    }
130
131    numComponents = this.rdnComponents.length;
132    dnString      = null;
133    normalizedDN  = null;
134  }
135
136
137
138  /**
139   * Creates a new DN with the provided set of RDNs, arranged with the
140   * suffix as the last element.
141   *
142   * @param  rdnComponents  The set of RDN components that make up
143   *                        this DN.
144   */
145  public DN(List<RDN> rdnComponents)
146  {
147    if (rdnComponents == null || rdnComponents.isEmpty())
148    {
149      this.rdnComponents = new RDN[0];
150    }
151    else
152    {
153      this.rdnComponents = new RDN[rdnComponents.size()];
154      rdnComponents.toArray(this.rdnComponents);
155    }
156
157    numComponents = this.rdnComponents.length;
158    dnString      = null;
159    normalizedDN  = null;
160  }
161
162
163
164  /**
165   * Creates a new DN with the given RDN below the specified parent.
166   *
167   * @param  rdn       The RDN to use for the new DN.  It must not be
168   *                   {@code null}.
169   * @param  parentDN  The DN of the entry below which the new DN
170   *                   should exist. It must not be {@code null}.
171   */
172  public DN(RDN rdn, DN parentDN)
173  {
174    ifNull(rdn, parentDN);
175    if (parentDN.isRootDN())
176    {
177      rdnComponents = new RDN[] { rdn };
178    }
179    else
180    {
181      rdnComponents = new RDN[parentDN.numComponents + 1];
182      rdnComponents[0] = rdn;
183      System.arraycopy(parentDN.rdnComponents, 0, rdnComponents, 1,
184                       parentDN.numComponents);
185    }
186
187    numComponents = this.rdnComponents.length;
188    dnString      = null;
189    normalizedDN  = null;
190  }
191
192
193
194  /**
195   * Retrieves a singleton instance of the null DN.
196   *
197   * @return  A singleton instance of the null DN.
198   */
199  public static DN rootDN()
200  {
201    return NULL_DN;
202  }
203
204
205
206  /**
207   * Indicates whether this represents a null DN.  This could target
208   * the root DSE for the Directory Server, or the authorization DN
209   * for an anonymous or unauthenticated client.
210   *
211   * @return  <CODE>true</CODE> if this does represent a null DN, or
212   *          <CODE>false</CODE> if it does not.
213   */
214  public boolean isRootDN()
215  {
216    return numComponents == 0;
217  }
218
219
220
221  /**
222   * Retrieves the number of RDN components for this DN.
223   *
224   * @return  The number of RDN components for this DN.
225   */
226  public int size()
227  {
228    return numComponents;
229  }
230
231
232
233  /**
234   * Retrieves the outermost RDN component for this DN (i.e., the one
235   * that is furthest from the suffix).
236   *
237   * @return  The outermost RDN component for this DN, or
238   *          <CODE>null</CODE> if there are no RDN components in the
239   *          DN.
240   */
241  public RDN rdn()
242  {
243    if (numComponents == 0)
244    {
245      return null;
246    }
247    else
248    {
249      return rdnComponents[0];
250    }
251  }
252
253  /**
254   * Returns a copy of this DN whose parent DN, {@code fromDN}, has been renamed
255   * to the new parent DN, {@code toDN}. If this DN is not subordinate or equal
256   * to {@code fromDN} then this DN is returned (i.e. the DN is not renamed).
257   *
258   * @param fromDN
259   *          The old parent DN.
260   * @param toDN
261   *          The new parent DN.
262   * @return The renamed DN, or this DN if no renaming was performed.
263   */
264  public DN rename(final DN fromDN, final DN toDN)
265  {
266    Reject.ifNull(fromDN, toDN);
267
268    if (!isDescendantOf(fromDN))
269    {
270      return this;
271    }
272    else if (equals(fromDN))
273    {
274      return toDN;
275    }
276    else
277    {
278      final int sizeOfRdns = size() - fromDN.size();
279      RDN[] childRdns = new RDN[sizeOfRdns];
280      System.arraycopy(rdnComponents, 0, childRdns, 0, sizeOfRdns);
281      return toDN.concat(childRdns);
282    }
283  }
284
285
286
287  /**
288   * Retrieves the RDN component at the specified position in the set
289   * of components for this DN.
290   *
291   * @param  pos  The position of the RDN component to retrieve.
292   *
293   * @return  The RDN component at the specified position in the set
294   *          of components for this DN.
295   */
296  public RDN getRDN(int pos)
297  {
298    return rdnComponents[pos];
299  }
300
301
302
303  /**
304   * Retrieves the DN of the entry that is the immediate parent for
305   * this entry.  Note that this method does not take the server's
306   * naming context configuration into account when making the
307   * determination.
308   *
309   * @return  The DN of the entry that is the immediate parent for
310   *          this entry, or <CODE>null</CODE> if the entry with this
311   *          DN does not have a parent.
312   */
313  public DN parent()
314  {
315    if (numComponents <= 1)
316    {
317      return null;
318    }
319
320    RDN[] parentComponents = new RDN[numComponents-1];
321    System.arraycopy(rdnComponents, 1, parentComponents, 0,
322                     numComponents-1);
323    return new DN(parentComponents);
324  }
325
326
327
328  /**
329   * Retrieves the DN of the entry that is the immediate parent for
330   * this entry.  This method does take the server's naming context
331   * configuration into account, so if the current DN is a naming
332   * context for the server, then it will not be considered to have a
333   * parent.
334   *
335   * @return  The DN of the entry that is the immediate parent for
336   *          this entry, or <CODE>null</CODE> if the entry with this
337   *          DN does not have a parent (either because there is only
338   *          a single RDN component or because this DN is a suffix
339   *          defined in the server).
340   */
341  public DN getParentDNInSuffix()
342  {
343    if (numComponents <= 1 || DirectoryServer.isNamingContext(this))
344    {
345      return null;
346    }
347
348    RDN[] parentComponents = new RDN[numComponents-1];
349    System.arraycopy(rdnComponents, 1, parentComponents, 0, numComponents-1);
350    return new DN(parentComponents);
351  }
352
353
354
355  /**
356   * Creates a new DN that is a child of this DN, using the specified
357   * RDN.
358   *
359   * @param  rdn  The RDN for the child of this DN.
360   *
361   * @return  A new DN that is a child of this DN, using the specified
362   *          RDN.
363   */
364  public DN child(RDN rdn)
365  {
366    RDN[] newComponents = new RDN[rdnComponents.length+1];
367    newComponents[0] = rdn;
368    System.arraycopy(rdnComponents, 0, newComponents, 1,
369                     rdnComponents.length);
370
371    return new DN(newComponents);
372  }
373
374
375
376  /**
377   * Creates a new DN that is a descendant of this DN, using the
378   * specified RDN components.
379   *
380   * @param  rdnComponents  The RDN components for the descendant of
381   *                        this DN.
382   *
383   * @return  A new DN that is a descendant of this DN, using the
384   *          specified RDN components.
385   */
386  public DN concat(RDN[] rdnComponents)
387  {
388    RDN[] newComponents =
389         new RDN[rdnComponents.length+this.rdnComponents.length];
390    System.arraycopy(rdnComponents, 0, newComponents, 0,
391                     rdnComponents.length);
392    System.arraycopy(this.rdnComponents, 0, newComponents,
393                     rdnComponents.length, this.rdnComponents.length);
394
395    return new DN(newComponents);
396  }
397
398
399
400  /**
401   * Creates a new DN that is a descendant of this DN, using the
402   * specified DN as a relative base DN.  That is, the resulting DN
403   * will first have the components of the provided DN followed by the
404   * components of this DN.
405   *
406   * @param  relativeBaseDN  The relative base DN to concatenate onto
407   *                         this DN.
408   *
409   * @return  A new DN that is a descendant of this DN, using the
410   *          specified DN as a relative base DN.
411   */
412  public DN child(DN relativeBaseDN)
413  {
414    RDN[] newComponents =
415               new RDN[rdnComponents.length+
416                       relativeBaseDN.rdnComponents.length];
417
418    System.arraycopy(relativeBaseDN.rdnComponents, 0, newComponents,
419                     0, relativeBaseDN.rdnComponents.length);
420    System.arraycopy(rdnComponents, 0, newComponents,
421                     relativeBaseDN.rdnComponents.length,
422                     rdnComponents.length);
423
424    return new DN(newComponents);
425  }
426
427
428
429  /**
430   * Indicates whether this DN is a descendant of the provided DN
431   * (i.e., that the RDN components of the provided DN are the
432   * same as the last RDN components for this DN).  Note that if
433   * this DN equals the provided DN it is still considered to be
434   * a descendant of the provided DN by this method as both then
435   * reside within the same subtree.
436   *
437   * @param  dn  The DN for which to make the determination.
438   *
439   * @return  <CODE>true</CODE> if this DN is a descendant of the
440   *          provided DN, or <CODE>false</CODE> if not.
441   */
442  public boolean isDescendantOf(DN dn)
443  {
444    int offset = numComponents - dn.numComponents;
445    if (offset < 0)
446    {
447      return false;
448    }
449
450    for (int i=0; i < dn.numComponents; i++)
451    {
452      if (! rdnComponents[i+offset].equals(dn.rdnComponents[i]))
453      {
454        return false;
455      }
456    }
457
458    return true;
459  }
460
461
462
463  /**
464   * Indicates whether this DN is an ancestor of the provided DN
465   * (i.e., that the RDN components of this DN are the same as the
466   * last RDN components for the provided DN).
467   *
468   * @param  dn  The DN for which to make the determination.
469   *
470   * @return  <CODE>true</CODE> if this DN is an ancestor of the
471   *          provided DN, or <CODE>false</CODE> if not.
472   */
473  public boolean isAncestorOf(DN dn)
474  {
475    int offset = dn.numComponents - numComponents;
476    if (offset < 0)
477    {
478      return false;
479    }
480
481    for (int i=0; i < numComponents; i++)
482    {
483      if (! rdnComponents[i].equals(dn.rdnComponents[i+offset]))
484      {
485        return false;
486      }
487    }
488
489    return true;
490  }
491
492
493
494  /**
495   * Indicates whether this entry falls within the range of the
496   * provided search base DN and scope.
497   *
498   * @param  baseDN  The base DN for which to make the determination.
499   * @param  scope   The search scope for which to make the
500   *                 determination.
501   *
502   * @return  <CODE>true</CODE> if this entry is within the given
503   *          base and scope, or <CODE>false</CODE> if it is not.
504   */
505  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
506  {
507    switch (scope.asEnum())
508    {
509      case BASE_OBJECT:
510        // The base DN must equal this DN.
511        return equals(baseDN);
512
513      case SINGLE_LEVEL:
514        // The parent DN must equal the base DN.
515        return baseDN.equals(parent());
516
517      case WHOLE_SUBTREE:
518        // This DN must be a descendant of the provided base DN.
519        return isDescendantOf(baseDN);
520
521      case SUBORDINATES:
522        // This DN must be a descendant of the provided base DN, but
523        // not equal to it.
524        return !equals(baseDN) && isDescendantOf(baseDN);
525
526      default:
527        // This is a scope that we don't recognize.
528        return false;
529    }
530  }
531
532  /**
533   * Decodes the provided ASN.1 octet string as a DN.
534   *
535   * @param  dnString  The ASN.1 octet string to decode as a DN.
536   *
537   * @return  The decoded DN.
538   *
539   * @throws  DirectoryException  If a problem occurs while trying to
540   *                              decode the provided ASN.1 octet
541   *                              string as a DN.
542   */
543  public static DN decode(ByteSequence dnString)
544         throws DirectoryException
545  {
546    // A null or empty DN is acceptable.
547    if (dnString == null)
548    {
549      return NULL_DN;
550    }
551
552    int    length  = dnString.length();
553    if (length == 0)
554    {
555      return NULL_DN;
556    }
557
558
559    // See if we are dealing with any non-ASCII characters, or any
560    // escaped characters.  If so, then the easiest and safest
561    // approach is to convert the DN to a string and decode it that
562    // way.
563    byte b;
564    for (int i = 0; i < length; i++)
565    {
566      b = dnString.byteAt(i);
567      if ((b & 0x7F) != b || b == '\\')
568      {
569        return valueOf(dnString.toString());
570      }
571    }
572
573
574    // Iterate through the DN string.  The first thing to do is to get
575    // rid of any leading spaces.
576    ByteSequenceReader dnReader = dnString.asReader();
577    b = ' ';
578    while (dnReader.remaining() > 0 && (b = dnReader.get()) == ' ')
579    {}
580
581    if(b == ' ')
582    {
583      // This means that the DN was completely comprised of spaces
584      // and therefore should be considered the same as a null or
585      // empty DN.
586      return NULL_DN;
587    }
588
589    dnReader.skip(-1);
590    // We know that it's not an empty DN, so we can do the real
591    // processing.  Create a loop and iterate through all the RDN
592    // components.
593    boolean allowExceptions =
594         DirectoryServer.allowAttributeNameExceptions();
595    LinkedList<RDN> rdnComponents = new LinkedList<>();
596    while (true)
597    {
598      ByteString attributeName =
599          parseAttributeName(dnReader, allowExceptions);
600
601
602      // Make sure that we're not at the end of the DN string because
603      // that would be invalid.
604      if (dnReader.remaining() <= 0)
605      {
606        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
607            ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
608      }
609
610
611      // Skip over any spaces between the attribute name and its value.
612      b = ' ';
613      while (dnReader.remaining() > 0 && (b = dnReader.get()) == ' ')
614      {}
615
616
617      if(b == ' ')
618      {
619        // This means that we hit the end of the value before
620        // finding a '='.  This is illegal because there is no
621        // attribute-value separator.
622        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
623            ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
624      }
625
626      // The next character must be an equal sign.  If it is not,
627      // then that's an error.
628      if (b != '=')
629      {
630        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
631            ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, attributeName, (char) b));
632      }
633
634
635      // Skip over any spaces after the equal sign.
636      b = ' ';
637      while (dnReader.remaining() > 0 && (b = dnReader.get()) == ' ')
638      {}
639
640
641      // If we are at the end of the DN string, then that must mean
642      // that the attribute value was empty.  This will probably never
643      // happen in a real-world environment, but technically isn't
644      // illegal.  If it does happen, then go ahead and create the RDN
645      // component and return the DN.
646      if (b == ' ')
647      {
648        StringBuilder lowerName = new StringBuilder();
649        toLowerCase(attributeName, lowerName, true);
650        String attributeNameString = attributeName.toString();
651        AttributeType attrType = getAttributeType(lowerName.toString(), attributeNameString);
652        rdnComponents.add(
653            new RDN(attrType, attributeNameString, ByteString.empty()));
654        return new DN(rdnComponents);
655      }
656
657      dnReader.skip(-1);
658
659      // Parse the value for this RDN component.
660      ByteString parsedValue = parseAttributeValue(dnReader);
661
662
663      // Create the new RDN with the provided information.
664      StringBuilder lowerName = new StringBuilder();
665      toLowerCase(attributeName, lowerName, true);
666      AttributeType attrType  =
667          DirectoryServer.getAttributeType(lowerName.toString());
668      String attributeNameString = attributeName.toString();
669      if (attrType == null)
670      {
671        // This must be an attribute type that we don't know about.
672        // In that case, we'll create a new attribute using the
673        // default syntax.  If this is a problem, it will be caught
674        // later either by not finding the target entry or by not
675        // allowing the entry to be added.
676        attrType = DirectoryServer.getDefaultAttributeType(
677            attributeNameString);
678      }
679
680      RDN rdn = new RDN(attrType, attributeNameString, parsedValue);
681
682
683      // Skip over any spaces that might be after the attribute value.
684      b = ' ';
685      while (dnReader.remaining() > 0 && (b = dnReader.get()) == ' ')
686      {}
687
688
689      // Most likely, we will be at either the end of the RDN
690      // component or the end of the DN.  If so, then handle that
691      // appropriately.
692      if (b == ' ')
693      {
694        // We're at the end of the DN string and should have a valid
695        // DN so return it.
696        rdnComponents.add(rdn);
697        return new DN(rdnComponents);
698      }
699      else if (b == ',' || b == ';')
700      {
701        // We're at the end of the RDN component, so add it to the
702        // list, skip over the comma/semicolon, and start on the next
703        // component.
704        rdnComponents.add(rdn);
705        continue;
706      }
707      else if (b != '+')
708      {
709        // This should not happen.  At any rate, it's an illegal
710        // character, so throw an exception.
711        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
712            ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(
713                dnReader, (char) b, dnReader.position()-1));
714      }
715
716
717      // If we have gotten here, then this must be a multi-valued RDN.
718      // In that case, parse the remaining attribute/value pairs and
719      // add them to the RDN that we've already created.
720      while (true)
721      {
722        // Skip over the plus sign and any spaces that may follow it
723        // before the next attribute name.
724        b = ' ';
725        while (dnReader.remaining() > 0 &&
726            (b = dnReader.get()) == ' ')
727        {}
728
729        dnReader.skip(-1);
730        // Parse the attribute name from the DN string.
731        attributeName = parseAttributeName(dnReader, allowExceptions);
732
733
734        // Make sure that we're not at the end of the DN string
735        // because that would be invalid.
736        if (b == ' ')
737        {
738          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
739              ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
740        }
741
742
743        // Skip over any spaces between the attribute name and its value.
744        b = ' ';
745        while (dnReader.remaining() > 0 &&
746            (b = dnReader.get()) == ' ')
747        {}
748
749        if(b == ' ')
750        {
751          // This means that we hit the end of the value before
752          // finding a '='.  This is illegal because there is no
753          // attribute-value separator.
754          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
755              ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
756        }
757
758
759        // The next character must be an equal sign.  If it is not,
760        // then that's an error.
761        if (b != '=')
762        {
763          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
764              ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, attributeName, (char) b));
765        }
766
767
768        // Skip over any spaces after the equal sign.
769        b = ' ';
770        while (dnReader.remaining() > 0 &&
771            (b = dnReader.get()) == ' ')
772        {}
773
774
775        // If we are at the end of the DN string, then that must mean
776        // that the attribute value was empty.  This will probably
777        // never happen in a real-world environment, but technically
778        // isn't illegal.  If it does happen, then go ahead and create
779        // the RDN component and return the DN.
780        if (b == ' ')
781        {
782          lowerName = new StringBuilder();
783          toLowerCase(attributeName, lowerName, true);
784          attrType =
785              DirectoryServer.getAttributeType(lowerName.toString());
786          attributeNameString = attributeName.toString();
787
788          if (attrType == null)
789          {
790            // This must be an attribute type that we don't know
791            // about.  In that case, we'll create a new attribute
792            // using the default syntax.  If this is a problem, it
793            // will be caught later either by not finding the target
794            // entry or by not allowing the entry to be added.
795            attrType = DirectoryServer.getDefaultAttributeType(
796                attributeNameString);
797          }
798
799          rdn.addValue(attrType, attributeNameString, ByteString.empty());
800          rdnComponents.add(rdn);
801          return new DN(rdnComponents);
802        }
803
804        dnReader.skip(-1);
805
806        // Parse the value for this RDN component.
807        parsedValue = parseAttributeValue(dnReader);
808
809
810        lowerName = new StringBuilder();
811        toLowerCase(attributeName, lowerName, true);
812        attrType =
813            DirectoryServer.getAttributeType(lowerName.toString());
814        attributeNameString = attributeName.toString();
815        if (attrType == null)
816        {
817          // This must be an attribute type that we don't know about.
818          // In that case, we'll create a new attribute using the
819          // default syntax.  If this is a problem, it will be caught
820          // later either by not finding the target entry or by not
821          // allowing the entry to be added.
822          attrType = DirectoryServer.getDefaultAttributeType(
823              attributeNameString);
824        }
825
826        rdn.addValue(attrType, attributeNameString, parsedValue);
827
828
829        // Skip over any spaces that might be after the attribute value.
830        // Skip over any spaces that might be after the attribute value.
831        b = ' ';
832        while (dnReader.remaining() > 0 &&
833            (b = dnReader.get()) == ' ')
834        {}
835
836
837        // Most likely, we will be at either the end of the RDN
838        // component or the end of the DN.  If so, then handle that
839        // appropriately.
840        if (b == ' ')
841        {
842          // We're at the end of the DN string and should have a valid
843          // DN so return it.
844          rdnComponents.add(rdn);
845          return new DN(rdnComponents);
846        }
847        else if (b == ',' || b == ';')
848        {
849          // We're at the end of the RDN component, so add it to the
850          // list, skip over the comma/semicolon, and start on the
851          // next component.
852          rdnComponents.add(rdn);
853          break;
854        }
855        else if (b != '+')
856        {
857          // This should not happen.  At any rate, it's an illegal
858          // character, so throw an exception.
859          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
860              ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(
861                  dnString, (char) b, dnReader.position()-1));
862        }
863      }
864    }
865  }
866
867  /**
868   * Decodes the provided string as a DN.
869   *
870   * @param  dnString  The string to decode as a DN.
871   *
872   * @return  The decoded DN.
873   *
874   * @throws  DirectoryException  If a problem occurs while trying to
875   *                              decode the provided string as a DN.
876   */
877  public static DN valueOf(String dnString)
878         throws DirectoryException
879  {
880    // A null or empty DN is acceptable.
881    if (dnString == null)
882    {
883      return NULL_DN;
884    }
885
886    int length = dnString.length();
887    if (length == 0)
888    {
889      return NULL_DN;
890    }
891
892
893    // Iterate through the DN string.  The first thing to do is to get
894    // rid of any leading spaces.
895    int pos = 0;
896    char c = dnString.charAt(pos);
897    while (c == ' ')
898    {
899      pos++;
900      if (pos == length)
901      {
902        // This means that the DN was completely comprised of spaces
903        // and therefore should be considered the same as a null or
904        // empty DN.
905        return NULL_DN;
906      }
907      else
908      {
909        c = dnString.charAt(pos);
910      }
911    }
912
913
914    // We know that it's not an empty DN, so we can do the real
915    // processing.  Create a loop and iterate through all the RDN
916    // components.
917    boolean allowExceptions =
918         DirectoryServer.allowAttributeNameExceptions();
919    LinkedList<RDN> rdnComponents = new LinkedList<>();
920    while (true)
921    {
922      StringBuilder attributeName = new StringBuilder();
923      pos = parseAttributeName(dnString, pos, attributeName,
924                               allowExceptions);
925
926
927      // Make sure that we're not at the end of the DN string because
928      // that would be invalid.
929      if (pos >= length)
930      {
931        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
932            ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
933      }
934
935
936      // Skip over any spaces between the attribute name and its value.
937      c = dnString.charAt(pos);
938      while (c == ' ')
939      {
940        pos++;
941        if (pos >= length)
942        {
943          // This means that we hit the end of the value before
944          // finding a '='.  This is illegal because there is no
945          // attribute-value separator.
946          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
947              ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
948        }
949        c = dnString.charAt(pos);
950      }
951
952
953      // The next character must be an equal sign.  If it is not, then
954      // that's an error.
955      if (c == '=')
956      {
957        pos++;
958      }
959      else
960      {
961        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
962            ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, attributeName, c));
963      }
964
965
966      // Skip over any spaces after the equal sign.
967      while (pos < length && ((c = dnString.charAt(pos)) == ' '))
968      {
969        pos++;
970      }
971
972
973      // If we are at the end of the DN string, then that must mean
974      // that the attribute value was empty.  This will probably never
975      // happen in a real-world environment, but technically isn't
976      // illegal.  If it does happen, then go ahead and create the
977      // RDN component and return the DN.
978      if (pos >= length)
979      {
980        String        name      = attributeName.toString();
981        String        lowerName = toLowerCase(name);
982        AttributeType attrType = getAttributeType(lowerName, name);
983        rdnComponents.add(new RDN(attrType, name, ByteString.empty()));
984        return new DN(rdnComponents);
985      }
986
987
988      // Parse the value for this RDN component.
989      ByteStringBuilder parsedValue = new ByteStringBuilder(0);
990      pos = parseAttributeValue(dnString, pos, parsedValue);
991
992
993      // Create the new RDN with the provided information.
994      String name            = attributeName.toString();
995      String lowerName       = toLowerCase(name);
996      AttributeType attrType = getAttributeType(lowerName, name);
997      RDN rdn = new RDN(attrType, name, parsedValue.toByteString());
998
999
1000      // Skip over any spaces that might be after the attribute value.
1001      while (pos < length && ((c = dnString.charAt(pos)) == ' '))
1002      {
1003        pos++;
1004      }
1005
1006
1007      // Most likely, we will be at either the end of the RDN
1008      // component or the end of the DN.  If so, then handle that
1009      // appropriately.
1010      if (pos >= length)
1011      {
1012        // We're at the end of the DN string and should have a valid
1013        // DN so return it.
1014        rdnComponents.add(rdn);
1015        return new DN(rdnComponents);
1016      }
1017      else if (c == ',' || c == ';')
1018      {
1019        // We're at the end of the RDN component, so add it to the
1020        // list, skip over the comma/semicolon, and start on the next
1021        // component.
1022        rdnComponents.add(rdn);
1023        pos++;
1024        continue;
1025      }
1026      else if (c != '+')
1027      {
1028        // This should not happen.  At any rate, it's an illegal
1029        // character, so throw an exception.
1030        LocalizableMessage message =
1031            ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(dnString, c, pos);
1032        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1033                                     message);
1034      }
1035
1036
1037      // If we have gotten here, then this must be a multi-valued RDN.
1038      // In that case, parse the remaining attribute/value pairs and
1039      // add them to the RDN that we've already created.
1040      while (true)
1041      {
1042        // Skip over the plus sign and any spaces that may follow it
1043        // before the next attribute name.
1044        pos++;
1045        while (pos < length && dnString.charAt(pos) == ' ')
1046        {
1047          pos++;
1048        }
1049
1050
1051        // Parse the attribute name from the DN string.
1052        attributeName = new StringBuilder();
1053        pos = parseAttributeName(dnString, pos, attributeName,
1054                                 allowExceptions);
1055
1056
1057        // Make sure that we're not at the end of the DN string
1058        // because that would be invalid.
1059        if (pos >= length)
1060        {
1061          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1062              ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
1063        }
1064
1065
1066        // Skip over any spaces between the attribute name and its value.
1067        c = dnString.charAt(pos);
1068        while (c == ' ')
1069        {
1070          pos++;
1071          if (pos >= length)
1072          {
1073            // This means that we hit the end of the value before
1074            // finding a '='.  This is illegal because there is no
1075            // attribute-value separator.
1076            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1077                ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName));
1078          }
1079          c = dnString.charAt(pos);
1080        }
1081
1082
1083        // The next character must be an equal sign.  If it is not,
1084        // then that's an error.
1085        if (c == '=')
1086        {
1087          pos++;
1088        }
1089        else
1090        {
1091          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1092              ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, attributeName, c));
1093        }
1094
1095
1096        // Skip over any spaces after the equal sign.
1097        while (pos < length && ((c = dnString.charAt(pos)) == ' '))
1098        {
1099          pos++;
1100        }
1101
1102
1103        // If we are at the end of the DN string, then that must mean
1104        // that the attribute value was empty.  This will probably
1105        // never happen in a real-world environment, but technically
1106        // isn't illegal.  If it does happen, then go ahead and create
1107        // the RDN component and return the DN.
1108        if (pos >= length)
1109        {
1110          name      = attributeName.toString();
1111          lowerName = toLowerCase(name);
1112          attrType  = DirectoryServer.getAttributeType(lowerName);
1113
1114          if (attrType == null)
1115          {
1116            // This must be an attribute type that we don't know
1117            // about.  In that case, we'll create a new attribute
1118            // using the default syntax.  If this is a problem, it
1119            // will be caught later either by not finding the target
1120            // entry or by not allowing the entry to be added.
1121            attrType = DirectoryServer.getDefaultAttributeType(name);
1122          }
1123
1124          rdn.addValue(attrType, name, ByteString.empty());
1125          rdnComponents.add(rdn);
1126          return new DN(rdnComponents);
1127        }
1128
1129
1130        // Parse the value for this RDN component.
1131        parsedValue.clear();
1132        pos = parseAttributeValue(dnString, pos, parsedValue);
1133
1134
1135        // Create the new RDN with the provided information.
1136        name      = attributeName.toString();
1137        lowerName = toLowerCase(name);
1138        attrType  = DirectoryServer.getAttributeType(lowerName);
1139        if (attrType == null)
1140        {
1141          // This must be an attribute type that we don't know about.
1142          // In that case, we'll create a new attribute using the
1143          // default syntax.  If this is a problem, it will be caught
1144          // later either by not finding the target entry or by not
1145          // allowing the entry to be added.
1146          attrType = DirectoryServer.getDefaultAttributeType(name);
1147        }
1148
1149        rdn.addValue(attrType, name, parsedValue.toByteString());
1150
1151
1152        // Skip over any spaces that might be after the attribute value.
1153        while (pos < length && ((c = dnString.charAt(pos)) == ' '))
1154        {
1155          pos++;
1156        }
1157
1158
1159        // Most likely, we will be at either the end of the RDN
1160        // component or the end of the DN.  If so, then handle that
1161        // appropriately.
1162        if (pos >= length)
1163        {
1164          // We're at the end of the DN string and should have a valid
1165          // DN so return it.
1166          rdnComponents.add(rdn);
1167          return new DN(rdnComponents);
1168        }
1169        else if (c == ',' || c == ';')
1170        {
1171          // We're at the end of the RDN component, so add it to the
1172          // list, skip over the comma/semicolon, and start on the
1173          // next component.
1174          rdnComponents.add(rdn);
1175          pos++;
1176          break;
1177        }
1178        else if (c != '+')
1179        {
1180          // This should not happen.  At any rate, it's an illegal
1181          // character, so throw an exception.
1182          LocalizableMessage message =
1183              ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(dnString, c, pos);
1184          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1185                                       message);
1186        }
1187      }
1188    }
1189  }
1190
1191  private static AttributeType getAttributeType(String lowerName, String name)
1192  {
1193    AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
1194    if (attrType == null)
1195    {
1196      // This must be an attribute type that we don't know about.
1197      // In that case, we'll create a new attribute using the
1198      // default syntax.  If this is a problem, it will be caught
1199      // later either by not finding the target entry or by not
1200      // allowing the entry to be added.
1201      attrType = DirectoryServer.getDefaultAttributeType(name);
1202    }
1203    return attrType;
1204  }
1205
1206  /**
1207   * Parses an attribute name from the provided DN string starting at
1208   * the specified location.
1209   *
1210   * @param  dnBytes          The byte array containing the DN to
1211   *                          parse.
1212   * @param  allowExceptions  Indicates whether to allow certain
1213   *                          exceptions to the strict requirements
1214   *                          for attribute names.
1215   *
1216   * @return  The parsed attribute name.
1217   *
1218   * @throws  DirectoryException  If it was not possible to parse a
1219   *                              valid attribute name from the
1220   *                              provided DN string.
1221   */
1222  static ByteString parseAttributeName(ByteSequenceReader dnBytes,
1223                                boolean allowExceptions)
1224          throws DirectoryException
1225  {
1226    // Skip over any leading spaces.
1227    while(dnBytes.remaining() > 0 && dnBytes.get() == ' ')
1228    {}
1229
1230    if(dnBytes.remaining() <= 0)
1231    {
1232      // This means that the remainder of the DN was completely
1233      // comprised of spaces.  If we have gotten here, then we
1234      // know that there is at least one RDN component, and
1235      // therefore the last non-space character of the DN must
1236      // have been a comma. This is not acceptable.
1237      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1238          ERR_ATTR_SYNTAX_DN_END_WITH_COMMA.get(dnBytes));
1239    }
1240
1241    dnBytes.skip(-1);
1242    int nameStartPos = dnBytes.position();
1243    ByteString nameBytes = null;
1244
1245    // Next, we should find the attribute name for this RDN component.
1246    // It may either be a name (with only letters, digits, and dashes
1247    // and starting with a letter) or an OID (with only digits and
1248    // periods, optionally prefixed with "oid."), and there is also a
1249    // special case in which we will allow underscores.  Because of
1250    // the complexity involved, read the entire name first with
1251    // minimal validation and then do more thorough validation later.
1252    boolean       checkForOID   = false;
1253    boolean       endOfName     = false;
1254    while (dnBytes.remaining() > 0)
1255    {
1256      // To make the switch more efficient, we'll include all ASCII
1257      // characters in the range of allowed values and then reject the
1258      // ones that aren't allowed.
1259      byte b = dnBytes.get();
1260      switch (b)
1261      {
1262        case ' ':
1263          // This should denote the end of the attribute name.
1264          endOfName = true;
1265          break;
1266
1267
1268        case '!':
1269        case '"':
1270        case '#':
1271        case '$':
1272        case '%':
1273        case '&':
1274        case '\'':
1275        case '(':
1276        case ')':
1277        case '*':
1278        case '+':
1279        case ',':
1280          // None of these are allowed in an attribute name or any
1281          // character immediately following it.
1282        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1283
1284
1285        case '-':
1286          // This will be allowed as long as it isn't the first
1287          // character in the attribute name.
1288          if (dnBytes.position() == nameStartPos + 1)
1289          {
1290            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1291                ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH.get(dnBytes));
1292          }
1293          break;
1294
1295
1296        case '.':
1297          // The period could be allowed if the attribute name is
1298          // actually expressed as an OID.  We'll accept it for now,
1299          // but make sure to check it later.
1300          checkForOID = true;
1301          break;
1302
1303
1304        case '/':
1305          // This is not allowed in an attribute name or any character
1306          // immediately following it.
1307          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1308
1309
1310        case '0':
1311        case '1':
1312        case '2':
1313        case '3':
1314        case '4':
1315        case '5':
1316        case '6':
1317        case '7':
1318        case '8':
1319        case '9':
1320          // Digits are always allowed if they are not the first
1321          // character.  However, they may be allowed if they are the
1322          // first character if the valid is an OID or if the
1323          // attribute name exceptions option is enabled.  Therefore,
1324          // we'll accept it now and check it later.
1325          break;
1326
1327
1328        case ':':
1329        case ';': // NOTE:  attribute options are not allowed in a DN.
1330        case '<':
1331          // None of these are allowed in an attribute name or any
1332          // character immediately following it.
1333          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1334
1335
1336        case '=':
1337          // This should denote the end of the attribute name.
1338          endOfName = true;
1339          break;
1340
1341
1342        case '>':
1343        case '?':
1344        case '@':
1345          // None of these are allowed in an attribute name or any
1346          // character immediately following it.
1347          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1348
1349
1350        case 'A':
1351        case 'B':
1352        case 'C':
1353        case 'D':
1354        case 'E':
1355        case 'F':
1356        case 'G':
1357        case 'H':
1358        case 'I':
1359        case 'J':
1360        case 'K':
1361        case 'L':
1362        case 'M':
1363        case 'N':
1364        case 'O':
1365        case 'P':
1366        case 'Q':
1367        case 'R':
1368        case 'S':
1369        case 'T':
1370        case 'U':
1371        case 'V':
1372        case 'W':
1373        case 'X':
1374        case 'Y':
1375        case 'Z':
1376          // These will always be allowed.
1377          break;
1378
1379
1380        case '[':
1381        case '\\':
1382        case ']':
1383        case '^':
1384          // None of these are allowed in an attribute name or any
1385          // character immediately following it.
1386          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1387
1388
1389        case '_':
1390          // This will never be allowed as the first character.  It
1391          // may be allowed for subsequent characters if the attribute
1392          // name exceptions option is enabled.
1393          if (dnBytes.position() == nameStartPos + 1)
1394          {
1395            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1396                ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE.get(
1397                    dnBytes, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS));
1398          }
1399          else if (!allowExceptions)
1400          {
1401            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1402                ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR.get(
1403                    dnBytes, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS));
1404          }
1405          break;
1406
1407
1408        case '`':
1409          // This is not allowed in an attribute name or any character
1410          // immediately following it.
1411          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1412
1413
1414        case 'a':
1415        case 'b':
1416        case 'c':
1417        case 'd':
1418        case 'e':
1419        case 'f':
1420        case 'g':
1421        case 'h':
1422        case 'i':
1423        case 'j':
1424        case 'k':
1425        case 'l':
1426        case 'm':
1427        case 'n':
1428        case 'o':
1429        case 'p':
1430        case 'q':
1431        case 'r':
1432        case 's':
1433        case 't':
1434        case 'u':
1435        case 'v':
1436        case 'w':
1437        case 'x':
1438        case 'y':
1439        case 'z':
1440          // These will always be allowed.
1441          break;
1442
1443
1444        default:
1445          // This is not allowed in an attribute name or any character
1446          // immediately following it.
1447          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, invalidChar(dnBytes, b));
1448      }
1449
1450
1451      if (endOfName)
1452      {
1453        int nameEndPos = dnBytes.position() - 1;
1454        dnBytes.position(nameStartPos);
1455        nameBytes =
1456            dnBytes.getByteString(nameEndPos - nameStartPos);
1457        break;
1458      }
1459    }
1460
1461
1462    // We should now have the full attribute name.  However, we may
1463    // still need to perform some validation, particularly if the name
1464    // contains a period or starts with a digit.  It must also have at
1465    // least one character.
1466    if (nameBytes == null || nameBytes.length() == 0)
1467    {
1468      LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_NO_NAME.get(dnBytes);
1469      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
1470    }
1471    else if (checkForOID)
1472    {
1473      boolean validOID = true;
1474
1475      int namePos = 0;
1476      int nameLength = nameBytes.length();
1477      byte ch0 = nameBytes.byteAt(0);
1478      if (ch0 == 'o' || ch0 == 'O')
1479      {
1480        if (nameLength <= 4)
1481        {
1482          validOID = false;
1483        }
1484        else
1485        {
1486          byte ch1 = nameBytes.byteAt(1);
1487          byte ch2 = nameBytes.byteAt(2);
1488          if ((ch1 == 'i' || ch1 == 'I')
1489              && (ch2 == 'd' || ch2 == 'D')
1490              && nameBytes.byteAt(3) == '.')
1491          {
1492            nameBytes = nameBytes.subSequence(4, nameBytes.length());
1493            nameLength -= 4;
1494          }
1495          else
1496          {
1497            validOID = false;
1498          }
1499        }
1500      }
1501
1502      while (validOID && namePos < nameLength)
1503      {
1504        byte ch = nameBytes.byteAt(namePos++);
1505        if (isDigit((char)ch))
1506        {
1507          while (validOID && namePos < nameLength &&
1508                 isDigit((char)nameBytes.byteAt(namePos)))
1509          {
1510            namePos++;
1511          }
1512
1513          if (namePos < nameLength && nameBytes.byteAt(namePos) != '.')
1514          {
1515            validOID = false;
1516          }
1517        }
1518        else if (ch == '.')
1519        {
1520          if (namePos == 1 || nameBytes.byteAt(namePos-2) == '.')
1521          {
1522            validOID = false;
1523          }
1524        }
1525        else
1526        {
1527          validOID = false;
1528        }
1529      }
1530
1531
1532      if (validOID && nameBytes.byteAt(nameLength-1) == '.')
1533      {
1534        validOID = false;
1535      }
1536
1537
1538      if (!validOID)
1539      {
1540        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1541            ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD.get(dnBytes, nameBytes));
1542      }
1543    }
1544    else if (isDigit((char)nameBytes.byteAt(0)) && !allowExceptions)
1545    {
1546      LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT.
1547          get(dnBytes, (char)nameBytes.byteAt(0),
1548              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
1549      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
1550    }
1551
1552    return nameBytes;
1553  }
1554
1555  private static LocalizableMessage invalidChar(ByteSequenceReader dnBytes, byte b)
1556  {
1557    return ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1558        dnBytes, (char) b, dnBytes.position()-1);
1559  }
1560
1561
1562  /**
1563   * Parses an attribute name from the provided DN string starting at
1564   * the specified location.
1565   *
1566   * @param  dnString         The DN string to be parsed.
1567   * @param  pos              The position at which to start parsing
1568   *                          the attribute name.
1569   * @param  attributeName    The buffer to which to append the parsed
1570   *                          attribute name.
1571   * @param  allowExceptions  Indicates whether to allow certain
1572   *                          exceptions to the strict requirements
1573   *                          for attribute names.
1574   *
1575   * @return  The position of the first character that is not part of
1576   *          the attribute name.
1577   *
1578   * @throws  DirectoryException  If it was not possible to parse a
1579   *                              valid attribute name from the
1580   *                              provided DN string.
1581   */
1582  static int parseAttributeName(String dnString, int pos,
1583                                StringBuilder attributeName,
1584                                boolean allowExceptions)
1585          throws DirectoryException
1586  {
1587    int length = dnString.length();
1588
1589
1590    // Skip over any leading spaces.
1591    if (pos < length)
1592    {
1593      while (dnString.charAt(pos) == ' ')
1594      {
1595        pos++;
1596        if (pos == length)
1597        {
1598          // This means that the remainder of the DN was completely
1599          // comprised of spaces.  If we have gotten here, then we
1600          // know that there is at least one RDN component, and
1601          // therefore the last non-space character of the DN must
1602          // have been a comma. This is not acceptable.
1603          LocalizableMessage message =
1604              ERR_ATTR_SYNTAX_DN_END_WITH_COMMA.get(dnString);
1605          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1606                                       message);
1607        }
1608      }
1609    }
1610
1611    // Next, we should find the attribute name for this RDN component.
1612    // It may either be a name (with only letters, digits, and dashes
1613    // and starting with a letter) or an OID (with only digits and
1614    // periods, optionally prefixed with "oid."), and there is also a
1615    // special case in which we will allow underscores.  Because of
1616    // the complexity involved, read the entire name first with
1617    // minimal validation and then do more thorough validation later.
1618    boolean       checkForOID   = false;
1619    boolean       endOfName     = false;
1620    while (pos < length)
1621    {
1622      // To make the switch more efficient, we'll include all ASCII
1623      // characters in the range of allowed values and then reject the
1624      // ones that aren't allowed.
1625      char c = dnString.charAt(pos);
1626      switch (c)
1627      {
1628        case ' ':
1629          // This should denote the end of the attribute name.
1630          endOfName = true;
1631          break;
1632
1633
1634        case '!':
1635        case '"':
1636        case '#':
1637        case '$':
1638        case '%':
1639        case '&':
1640        case '\'':
1641        case '(':
1642        case ')':
1643        case '*':
1644        case '+':
1645        case ',':
1646          // None of these are allowed in an attribute name or any
1647          // character immediately following it.
1648          LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1649              dnString, c, pos);
1650          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1651                                       message);
1652
1653
1654        case '-':
1655          // This will be allowed as long as it isn't the first
1656          // character in the attribute name.
1657          if (attributeName.length() > 0)
1658          {
1659            attributeName.append(c);
1660          }
1661          else
1662          {
1663            message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH.
1664                  get(dnString);
1665            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1666                                         message);
1667          }
1668          break;
1669
1670
1671        case '.':
1672          // The period could be allowed if the attribute name is
1673          // actually expressed as an OID.  We'll accept it for now,
1674          // but make sure to check it later.
1675          attributeName.append(c);
1676          checkForOID = true;
1677          break;
1678
1679
1680        case '/':
1681          // This is not allowed in an attribute name or any character
1682          // immediately following it.
1683          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1684              dnString, c, pos);
1685          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1686                                       message);
1687
1688
1689        case '0':
1690        case '1':
1691        case '2':
1692        case '3':
1693        case '4':
1694        case '5':
1695        case '6':
1696        case '7':
1697        case '8':
1698        case '9':
1699          // Digits are always allowed if they are not the first
1700          // character. However, they may be allowed if they are the
1701          // first character if the valid is an OID or if the
1702          // attribute name exceptions option is enabled.  Therefore,
1703          // we'll accept it now and check it later.
1704          attributeName.append(c);
1705          break;
1706
1707
1708        case ':':
1709        case ';': // NOTE:  attribute options are not allowed in a DN.
1710        case '<':
1711          // None of these are allowed in an attribute name or any
1712          // character immediately following it.
1713          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1714              dnString, c, pos);
1715          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1716                                       message);
1717
1718
1719        case '=':
1720          // This should denote the end of the attribute name.
1721          endOfName = true;
1722          break;
1723
1724
1725        case '>':
1726        case '?':
1727        case '@':
1728          // None of these are allowed in an attribute name or any
1729          // character immediately following it.
1730          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1731              dnString, c, pos);
1732          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1733                                       message);
1734
1735
1736        case 'A':
1737        case 'B':
1738        case 'C':
1739        case 'D':
1740        case 'E':
1741        case 'F':
1742        case 'G':
1743        case 'H':
1744        case 'I':
1745        case 'J':
1746        case 'K':
1747        case 'L':
1748        case 'M':
1749        case 'N':
1750        case 'O':
1751        case 'P':
1752        case 'Q':
1753        case 'R':
1754        case 'S':
1755        case 'T':
1756        case 'U':
1757        case 'V':
1758        case 'W':
1759        case 'X':
1760        case 'Y':
1761        case 'Z':
1762          // These will always be allowed.
1763          attributeName.append(c);
1764          break;
1765
1766
1767        case '[':
1768        case '\\':
1769        case ']':
1770        case '^':
1771          // None of these are allowed in an attribute name or any
1772          // character immediately following it.
1773          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1774              dnString, c, pos);
1775          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1776                                       message);
1777
1778
1779        case '_':
1780          // This will never be allowed as the first character.  It
1781          // may be allowed for subsequent characters if the attribute
1782          // name exceptions option is enabled.
1783          if (attributeName.length() == 0)
1784          {
1785            message =
1786                   ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE.
1787                  get(dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
1788            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1789                                         message);
1790          }
1791          else if (allowExceptions)
1792          {
1793            attributeName.append(c);
1794          }
1795          else
1796          {
1797            message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR.
1798                  get(dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
1799            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1800                                         message);
1801          }
1802          break;
1803
1804
1805        case '`':
1806          // This is not allowed in an attribute name or any character
1807          // immediately following it.
1808          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1809              dnString, c, pos);
1810          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1811                                       message);
1812
1813
1814        case 'a':
1815        case 'b':
1816        case 'c':
1817        case 'd':
1818        case 'e':
1819        case 'f':
1820        case 'g':
1821        case 'h':
1822        case 'i':
1823        case 'j':
1824        case 'k':
1825        case 'l':
1826        case 'm':
1827        case 'n':
1828        case 'o':
1829        case 'p':
1830        case 'q':
1831        case 'r':
1832        case 's':
1833        case 't':
1834        case 'u':
1835        case 'v':
1836        case 'w':
1837        case 'x':
1838        case 'y':
1839        case 'z':
1840          // These will always be allowed.
1841          attributeName.append(c);
1842          break;
1843
1844
1845        default:
1846          // This is not allowed in an attribute name or any character
1847          // immediately following it.
1848          message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
1849              dnString, c, pos);
1850          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1851                                       message);
1852      }
1853
1854
1855      if (endOfName)
1856      {
1857        break;
1858      }
1859
1860      pos++;
1861    }
1862
1863
1864    // We should now have the full attribute name.  However, we may
1865    // still need to perform some validation, particularly if the
1866    // name contains a period or starts with a digit.  It must also
1867    // have at least one character.
1868    if (attributeName.length() == 0)
1869    {
1870      LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_NO_NAME.get(dnString);
1871      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1872                                   message);
1873    }
1874    else if (checkForOID)
1875    {
1876      boolean validOID = true;
1877
1878      int namePos = 0;
1879      int nameLength = attributeName.length();
1880      char ch0 = attributeName.charAt(0);
1881      if (ch0 == 'o' || ch0 == 'O')
1882      {
1883        if (nameLength <= 4)
1884        {
1885          validOID = false;
1886        }
1887        else
1888        {
1889          char ch1 = attributeName.charAt(1);
1890          char ch2 = attributeName.charAt(2);
1891          if ((ch1 == 'i' || ch1 == 'I')
1892              && (ch2 == 'd' || ch2 == 'D')
1893              && attributeName.charAt(3) == '.')
1894          {
1895            attributeName.delete(0, 4);
1896            nameLength -= 4;
1897          }
1898          else
1899          {
1900            validOID = false;
1901          }
1902        }
1903      }
1904
1905      while (validOID && namePos < nameLength)
1906      {
1907        char ch = attributeName.charAt(namePos++);
1908        if (isDigit(ch))
1909        {
1910          while (validOID && namePos < nameLength &&
1911                 isDigit(attributeName.charAt(namePos)))
1912          {
1913            namePos++;
1914          }
1915
1916          if (namePos < nameLength && attributeName.charAt(namePos) != '.')
1917          {
1918            validOID = false;
1919          }
1920        }
1921        else if (ch == '.')
1922        {
1923          if (namePos == 1 || attributeName.charAt(namePos-2) == '.')
1924          {
1925            validOID = false;
1926          }
1927        }
1928        else
1929        {
1930          validOID = false;
1931        }
1932      }
1933
1934
1935      if (validOID && attributeName.charAt(nameLength-1) == '.')
1936      {
1937        validOID = false;
1938      }
1939
1940      if (! validOID)
1941      {
1942        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1943            ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD.get(dnString, attributeName));
1944      }
1945    }
1946    else if (isDigit(attributeName.charAt(0)) && !allowExceptions)
1947    {
1948      LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT.
1949          get(dnString, attributeName.charAt(0),
1950              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
1951      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message);
1952    }
1953
1954    return pos;
1955  }
1956
1957
1958
1959  /**
1960   * Parses the attribute value from the provided DN string starting
1961   * at the specified location.  When the value has been parsed, it
1962   * will be assigned to the provided ASN.1 octet string.
1963   *
1964   * @param  dnBytes         The byte array containing the DN to be
1965   *                         parsed.
1966   *
1967   * @return  The parsed attribute value.
1968   *
1969   * @throws  DirectoryException  If it was not possible to parse a
1970   *                              valid attribute value from the
1971   *                              provided DN string.
1972   */
1973  static ByteString parseAttributeValue(ByteSequenceReader dnBytes)
1974          throws DirectoryException
1975  {
1976    // All leading spaces have already been stripped so we can start
1977    // reading the value.  However, it may be empty so check for that.
1978    if (dnBytes.remaining() <= 0)
1979    {
1980      return ByteString.empty();
1981    }
1982
1983
1984    // Look at the first character.  If it is an octothorpe (#), then
1985    // that means that the value should be a hex string.
1986    byte b = dnBytes.get();
1987    if (b == '#')
1988    {
1989      // The first two characters must be hex characters.
1990      StringBuilder hexString = new StringBuilder();
1991      if (dnBytes.remaining() < 2)
1992      {
1993        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
1994            ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnBytes));
1995      }
1996
1997      for (int i=0; i < 2; i++)
1998      {
1999        b = dnBytes.get();
2000        if (isHexDigit(b))
2001        {
2002          hexString.append((char) b);
2003        }
2004        else
2005        {
2006          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2007              ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnBytes, (char) b));
2008        }
2009      }
2010
2011
2012      // The rest of the value must be a multiple of two hex
2013      // characters.  The end of the value may be designated by the
2014      // end of the DN, a comma or semicolon, a plus sign, or a space.
2015      while (dnBytes.remaining() > 0)
2016      {
2017        b = dnBytes.get();
2018        if (isHexDigit(b))
2019        {
2020          hexString.append((char) b);
2021
2022          if (dnBytes.remaining() > 0)
2023          {
2024            b = dnBytes.get();
2025            if (isHexDigit(b))
2026            {
2027              hexString.append((char) b);
2028            }
2029            else
2030            {
2031              throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2032                  ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnBytes, (char) b));
2033            }
2034          }
2035          else
2036          {
2037            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2038                ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnBytes));
2039          }
2040        }
2041        else if (b == ' ' || b == ',' || b == ';' || b == '+')
2042        {
2043          // This denotes the end of the value.
2044          dnBytes.skip(-1);
2045          break;
2046        }
2047        else
2048        {
2049          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2050              ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnBytes, (char) b));
2051        }
2052      }
2053
2054
2055      // At this point, we should have a valid hex string.  Convert it
2056      // to a byte array and set that as the value of the provided
2057      // octet string.
2058      try
2059      {
2060        return ByteString.wrap(hexStringToByteArray(hexString.toString()));
2061      }
2062      catch (Exception e)
2063      {
2064        logger.traceException(e);
2065
2066        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2067            ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(dnBytes, e));
2068      }
2069    }
2070
2071
2072    // If the first character is a quotation mark, then the value
2073    // should continue until the corresponding closing quotation mark.
2074    else if (b == '"')
2075    {
2076      int valueStartPos = dnBytes.position();
2077
2078      // Keep reading until we find a closing quotation mark.
2079      while (true)
2080      {
2081        if (dnBytes.remaining() <= 0)
2082        {
2083          // We hit the end of the DN before the closing quote.
2084          // That's an error.
2085          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2086              ERR_ATTR_SYNTAX_DN_UNMATCHED_QUOTE.get(dnBytes));
2087        }
2088
2089        if (dnBytes.get() == '"')
2090        {
2091          // This is the end of the value.
2092          break;
2093        }
2094      }
2095
2096      int valueEndPos = dnBytes.position();
2097      dnBytes.position(valueStartPos);
2098      ByteString bs = dnBytes.getByteString(valueEndPos - valueStartPos - 1);
2099      dnBytes.skip(1);
2100      return bs;
2101    }
2102
2103    else if(b == '+' || b == ',')
2104    {
2105      //We don't allow an empty attribute value. So do not allow the
2106      // first character to be a '+' or ',' since it is not escaped
2107      // by the user.
2108      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2109          ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR.get(dnBytes, dnBytes.position()));
2110    }
2111
2112    // Otherwise, use general parsing to find the end of the value.
2113    else
2114    {
2115      // Keep reading until we find a comma/semicolon, a plus sign, or
2116      // the end of the DN.
2117      int valueEndPos = dnBytes.position();
2118      int valueStartPos = valueEndPos - 1;
2119      while (true)
2120      {
2121        if (dnBytes.remaining() <= 0)
2122        {
2123          // This is the end of the DN and therefore the end of the value.
2124          break;
2125        }
2126
2127        b = dnBytes.get();
2128        if (b == ',' || b == ';' || b == '+')
2129        {
2130          dnBytes.skip(-1);
2131          break;
2132        }
2133
2134        if(b != ' ')
2135        {
2136          valueEndPos = dnBytes.position();
2137        }
2138      }
2139
2140
2141      // Convert the byte buffer to an array.
2142      dnBytes.position(valueStartPos);
2143      return dnBytes.getByteString(valueEndPos - valueStartPos);
2144    }
2145  }
2146
2147
2148
2149  /**
2150   * Parses the attribute value from the provided DN string starting
2151   * at the specified location.  When the value has been parsed, it
2152   * will be assigned to the provided ASN.1 octet string.
2153   *
2154   * @param  dnString        The DN string to be parsed.
2155   * @param  pos             The position of the first character in
2156   *                         the attribute value to parse.
2157   * @param  attributeValue  The ASN.1 octet string whose value should
2158   *                         be set to the parsed attribute value when
2159   *                         this method completes successfully.
2160   *
2161   * @return  The position of the first character that is not part of
2162   *          the attribute value.
2163   *
2164   * @throws  DirectoryException  If it was not possible to parse a
2165   *                              valid attribute value from the
2166   *                              provided DN string.
2167   */
2168  static int parseAttributeValue(String dnString, int pos,
2169                                 ByteStringBuilder attributeValue)
2170          throws DirectoryException
2171  {
2172    // All leading spaces have already been stripped so we can start
2173    // reading the value.  However, it may be empty so check for that.
2174    int length = dnString.length();
2175    if (pos >= length)
2176    {
2177      attributeValue.append("");
2178      return pos;
2179    }
2180
2181
2182    // Look at the first character.  If it is an octothorpe (#), then
2183    // that means that the value should be a hex string.
2184    char c = dnString.charAt(pos++);
2185    if (c == '#')
2186    {
2187      // The first two characters must be hex characters.
2188      StringBuilder hexString = new StringBuilder();
2189      if (pos+2 > length)
2190      {
2191        LocalizableMessage message =
2192            ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnString);
2193        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2194                                     message);
2195      }
2196
2197      for (int i=0; i < 2; i++)
2198      {
2199        c = dnString.charAt(pos++);
2200        if (isHexDigit(c))
2201        {
2202          hexString.append(c);
2203        }
2204        else
2205        {
2206          LocalizableMessage message =
2207              ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnString, c);
2208          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2209                                       message);
2210        }
2211      }
2212
2213
2214      // The rest of the value must be a multiple of two hex
2215      // characters.  The end of the value may be designated by the
2216      // end of the DN, a comma or semicolon, or a space.
2217      while (pos < length)
2218      {
2219        c = dnString.charAt(pos++);
2220        if (isHexDigit(c))
2221        {
2222          hexString.append(c);
2223
2224          if (pos < length)
2225          {
2226            c = dnString.charAt(pos++);
2227            if (isHexDigit(c))
2228            {
2229              hexString.append(c);
2230            }
2231            else
2232            {
2233              LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.
2234                  get(dnString, c);
2235              throw new DirectoryException(
2236                             ResultCode.INVALID_DN_SYNTAX, message);
2237            }
2238          }
2239          else
2240          {
2241            LocalizableMessage message =
2242                ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnString);
2243            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2244                                         message);
2245          }
2246        }
2247        else if (c == ' ' || c == ',' || c == ';')
2248        {
2249          // This denotes the end of the value.
2250          pos--;
2251          break;
2252        }
2253        else
2254        {
2255          LocalizableMessage message =
2256              ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnString, c);
2257          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2258                                       message);
2259        }
2260      }
2261
2262
2263      // At this point, we should have a valid hex string.  Convert it
2264      // to a byte array and set that as the value of the provided
2265      // octet string.
2266      try
2267      {
2268        attributeValue.append(
2269            hexStringToByteArray(hexString.toString()));
2270        return pos;
2271      }
2272      catch (Exception e)
2273      {
2274        logger.traceException(e);
2275
2276        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2277            ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(dnString, e));
2278      }
2279    }
2280
2281
2282    // If the first character is a quotation mark, then the value
2283    // should continue until the corresponding closing quotation mark.
2284    else if (c == '"')
2285    {
2286      // Keep reading until we find an unescaped closing quotation
2287      // mark.
2288      boolean escaped = false;
2289      StringBuilder valueString = new StringBuilder();
2290      while (true)
2291      {
2292        if (pos >= length)
2293        {
2294          // We hit the end of the DN before the closing quote.
2295          // That's an error.
2296          LocalizableMessage message =
2297              ERR_ATTR_SYNTAX_DN_UNMATCHED_QUOTE.get(dnString);
2298          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2299                                       message);
2300        }
2301
2302        c = dnString.charAt(pos++);
2303        if (escaped)
2304        {
2305          // The previous character was an escape, so we'll take this
2306          // one no matter what.
2307          valueString.append(c);
2308          escaped = false;
2309        }
2310        else if (c == '\\')
2311        {
2312          // The next character is escaped.  Set a flag to denote
2313          // this, but don't include the backslash.
2314          escaped = true;
2315        }
2316        else if (c == '"')
2317        {
2318          // This is the end of the value.
2319          break;
2320        }
2321        else
2322        {
2323          // This is just a regular character that should be in the value.
2324          valueString.append(c);
2325        }
2326      }
2327
2328      attributeValue.append(valueString.toString());
2329      return pos;
2330    }
2331    else if(c == '+' || c == ',')
2332    {
2333      //We don't allow an empty attribute value. So do not allow the
2334      // first character to be a '+' or ',' since it is not escaped
2335      // by the user.
2336      LocalizableMessage message =
2337             ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR.get(
2338                      dnString,pos);
2339          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2340                                       message);
2341    }
2342
2343
2344    // Otherwise, use general parsing to find the end of the value.
2345    else
2346    {
2347      boolean escaped;
2348      StringBuilder valueString = new StringBuilder();
2349      StringBuilder hexChars    = new StringBuilder();
2350
2351      if (c == '\\')
2352      {
2353        escaped = true;
2354      }
2355      else
2356      {
2357        escaped = false;
2358        valueString.append(c);
2359      }
2360
2361
2362      // Keep reading until we find an unescaped comma or plus sign or
2363      // the end of the DN.
2364      while (true)
2365      {
2366        if (pos >= length)
2367        {
2368          // This is the end of the DN and therefore the end of the
2369          // value.  If there are any hex characters, then we need to
2370          // deal with them accordingly.
2371          appendHexChars(dnString, valueString, hexChars);
2372          break;
2373        }
2374
2375        c = dnString.charAt(pos++);
2376        if (escaped)
2377        {
2378          // The previous character was an escape, so we'll take this
2379          // one.  However, this could be a hex digit, and if that's
2380          // the case then the escape would actually be in front of
2381          // two hex digits that should be treated as a special
2382          // character.
2383          if (isHexDigit(c))
2384          {
2385            // It is a hexadecimal digit, so the next digit must be
2386            // one too.  However, this could be just one in a series
2387            // of escaped hex pairs that is used in a string
2388            // containing one or more multi-byte UTF-8 characters so
2389            // we can't just treat this byte in isolation.  Collect
2390            // all the bytes together and make sure to take care of
2391            // these hex bytes before appending anything else to the value.
2392            if (pos >= length)
2393            {
2394              LocalizableMessage message =
2395                ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.
2396                    get(dnString);
2397              throw new DirectoryException(
2398                             ResultCode.INVALID_DN_SYNTAX, message);
2399            }
2400            else
2401            {
2402              char c2 = dnString.charAt(pos++);
2403              if (isHexDigit(c2))
2404              {
2405                hexChars.append(c);
2406                hexChars.append(c2);
2407              }
2408              else
2409              {
2410                LocalizableMessage message =
2411                  ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.
2412                      get(dnString);
2413                throw new DirectoryException(
2414                               ResultCode.INVALID_DN_SYNTAX, message);
2415              }
2416            }
2417          }
2418          else
2419          {
2420            appendHexChars(dnString, valueString, hexChars);
2421            valueString.append(c);
2422          }
2423
2424          escaped = false;
2425        }
2426        else if (c == '\\')
2427        {
2428          escaped = true;
2429        }
2430        else if (c == ',' || c == ';')
2431        {
2432          appendHexChars(dnString, valueString, hexChars);
2433          pos--;
2434          break;
2435        }
2436        else if (c == '+')
2437        {
2438          appendHexChars(dnString, valueString, hexChars);
2439          pos--;
2440          break;
2441        }
2442        else
2443        {
2444          appendHexChars(dnString, valueString, hexChars);
2445          valueString.append(c);
2446        }
2447      }
2448
2449
2450      // Strip off any unescaped spaces that may be at the end of the value.
2451      if (pos > 2 && dnString.charAt(pos-1) == ' ' &&
2452           dnString.charAt(pos-2) != '\\')
2453      {
2454        int lastPos = valueString.length() - 1;
2455        while (lastPos > 0)
2456        {
2457          if (valueString.charAt(lastPos) == ' ')
2458          {
2459            valueString.delete(lastPos, lastPos+1);
2460            lastPos--;
2461          }
2462          else
2463          {
2464            break;
2465          }
2466        }
2467      }
2468
2469
2470      attributeValue.append(valueString.toString());
2471      return pos;
2472    }
2473  }
2474
2475
2476
2477  /**
2478   * Decodes a hexadecimal string from the provided
2479   * <CODE>hexChars</CODE> buffer, converts it to a byte array, and
2480   * then converts that to a UTF-8 string.  The resulting UTF-8 string
2481   * will be appended to the provided <CODE>valueString</CODE> buffer,
2482   * and the <CODE>hexChars</CODE> buffer will be cleared.
2483   *
2484   * @param  dnString     The DN string that is being decoded.
2485   * @param  valueString  The buffer containing the value to which the
2486   *                      decoded string should be appended.
2487   * @param  hexChars     The buffer containing the hexadecimal
2488   *                      characters to decode to a UTF-8 string.
2489   *
2490   * @throws  DirectoryException  If any problem occurs during the
2491   *                              decoding process.
2492   */
2493  private static void appendHexChars(String dnString,
2494                                     StringBuilder valueString,
2495                                     StringBuilder hexChars)
2496          throws DirectoryException
2497  {
2498    if (hexChars.length() == 0)
2499    {
2500      return;
2501    }
2502
2503    try
2504    {
2505      byte[] hexBytes = hexStringToByteArray(hexChars.toString());
2506      valueString.append(new String(hexBytes, "UTF-8"));
2507      hexChars.delete(0, hexChars.length());
2508    }
2509    catch (Exception e)
2510    {
2511      logger.traceException(e);
2512
2513      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
2514          ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(dnString, e));
2515    }
2516  }
2517
2518
2519
2520  /**
2521   * Indicates whether the provided object is equal to this DN.  In
2522   * order for the object to be considered equal, it must be a DN with
2523   * the same number of RDN components and each corresponding RDN
2524   * component must be equal.
2525   *
2526   * @param  o  The object for which to make the determination.
2527   *
2528   * @return  <CODE>true</CODE> if the provided object is a DN that is
2529   *          equal to this DN, or <CODE>false</CODE> if it is not.
2530   */
2531  @Override
2532  public boolean equals(Object o)
2533  {
2534    if (this == o)
2535    {
2536      return true;
2537    }
2538
2539    if (o instanceof DN)
2540    {
2541      DN otherDN = (DN) o;
2542      return toNormalizedByteString().equals(otherDN.toNormalizedByteString());
2543    }
2544    return false;
2545  }
2546
2547  /**
2548   * Returns the hash code for this DN.
2549   *
2550   * @return  The hash code for this DN.
2551   */
2552  @Override
2553  public int hashCode()
2554  {
2555     return toNormalizedByteString().hashCode();
2556  }
2557
2558  /**
2559   * Retrieves a string representation of this DN.
2560   *
2561   * @return  A string representation of this DN.
2562   */
2563  @Override
2564  public String toString()
2565  {
2566    if (dnString == null)
2567    {
2568      if (numComponents == 0)
2569      {
2570        dnString = "";
2571      }
2572      else
2573      {
2574        StringBuilder buffer = new StringBuilder();
2575        rdnComponents[0].toString(buffer);
2576
2577        for (int i=1; i < numComponents; i++)
2578        {
2579          buffer.append(",");
2580          rdnComponents[i].toString(buffer);
2581        }
2582
2583        dnString = buffer.toString();
2584      }
2585    }
2586
2587    return dnString;
2588  }
2589
2590
2591
2592  /**
2593   * Appends a string representation of this DN to the provided
2594   * buffer.
2595   *
2596   * @param  buffer  The buffer to which the information should be
2597   *                 appended.
2598   */
2599  public void toString(StringBuilder buffer)
2600  {
2601    buffer.append(this);
2602  }
2603
2604  /**
2605   * Retrieves a normalized string representation of this DN.
2606   * <p>
2607   *
2608   * This representation is safe to use in an URL or in a file name.
2609   * However, it is not a valid DN and can't be reverted to a valid DN.
2610   *
2611   * @return  The normalized string representation of this DN.
2612   */
2613  public String toNormalizedUrlSafeString()
2614  {
2615    if (rdnComponents.length == 0)
2616    {
2617      return "";
2618    }
2619
2620    StringBuilder buffer = new StringBuilder();
2621    buffer.append(rdnComponents[0].toNormalizedUrlSafeString());
2622
2623    for (int i=1; i < rdnComponents.length; i++)
2624    {
2625      buffer.append(',');
2626      buffer.append(rdnComponents[i].toNormalizedUrlSafeString());
2627    }
2628
2629    return buffer.toString();
2630  }
2631
2632  /**
2633   * Retrieves a normalized byte string representation of this DN.
2634   * <p>
2635   * This representation is suitable for equality and comparisons, and for providing a
2636   * natural hierarchical ordering.
2637   * However, it is not a valid DN and can't be reverted to a valid DN.
2638   *
2639   * You should consider using a {@code CompactDn} as an alternative.
2640   *
2641   * @return  The normalized string representation of this DN.
2642   */
2643  public ByteString toNormalizedByteString()
2644  {
2645    if (normalizedDN == null)
2646    {
2647      if (numComponents == 0)
2648      {
2649        normalizedDN = ByteString.empty();
2650      }
2651      else
2652      {
2653        final ByteStringBuilder builder = new ByteStringBuilder();
2654        rdnComponents[numComponents - 1].toNormalizedByteString(builder);
2655        for (int i = numComponents - 2; i >= 0; i--)
2656        {
2657          builder.append(NORMALIZED_RDN_SEPARATOR);
2658          rdnComponents[i].toNormalizedByteString(builder);
2659        }
2660        normalizedDN = builder.toByteString();
2661      }
2662    }
2663    return normalizedDN;
2664  }
2665
2666  /**
2667   * Compares this DN with the provided DN based on a natural order, as defined by
2668   * the toNormalizedByteString() method.
2669   *
2670   * @param other
2671   *          The DN against which to compare this DN.
2672   * @return A negative integer if this DN should come before the provided DN, a
2673   *         positive integer if this DN should come after the provided DN, or
2674   *         zero if there is no difference with regard to ordering.
2675   */
2676  @Override
2677  public int compareTo(DN other)
2678  {
2679    return toNormalizedByteString().compareTo(other.toNormalizedByteString());
2680  }
2681}
2682