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