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