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