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