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