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