001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.nio.ByteBuffer; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.Iterator; 031import java.util.SortedSet; 032import java.util.TreeSet; 033 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.ldap.matchingrules.MatchingRule; 036import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 037import com.unboundid.ldap.sdk.schema.Schema; 038import com.unboundid.util.Debug; 039import com.unboundid.util.NotMutable; 040import com.unboundid.util.StaticUtils; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043import com.unboundid.util.Validator; 044 045import static com.unboundid.ldap.sdk.LDAPMessages.*; 046 047 048 049/** 050 * This class provides a data structure for holding information about an LDAP 051 * relative distinguished name (RDN). An RDN consists of one or more 052 * attribute name-value pairs. See 053 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more 054 * information about representing DNs and RDNs as strings. See the 055 * documentation in the {@link DN} class for more information about DNs and 056 * RDNs. 057 */ 058@NotMutable() 059@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 060public final class RDN 061 implements Comparable<RDN>, Comparator<RDN>, Serializable 062{ 063 /** 064 * The serial version UID for this serializable class. 065 */ 066 private static final long serialVersionUID = 2923419812807188487L; 067 068 069 070 // The set of attribute values for this RDN. 071 private final ASN1OctetString[] attributeValues; 072 073 // The schema to use to generate the normalized string representation of this 074 // RDN, if any. 075 private final Schema schema; 076 077 // The name-value pairs that comprise this RDN. 078 private volatile SortedSet<RDNNameValuePair> nameValuePairs; 079 080 // The normalized string representation for this RDN. 081 private volatile String normalizedString; 082 083 // The user-defined string representation for this RDN. 084 private volatile String rdnString; 085 086 // The set of attribute names for this RDN. 087 private final String[] attributeNames; 088 089 090 091 /** 092 * Creates a new single-valued RDN with the provided information. 093 * 094 * @param attributeName The attribute name for this RDN. It must not be 095 * {@code null}. 096 * @param attributeValue The attribute value for this RDN. It must not be 097 * {@code null}. 098 */ 099 public RDN(final String attributeName, final String attributeValue) 100 { 101 this(attributeName, attributeValue, null); 102 } 103 104 105 106 /** 107 * Creates a new single-valued RDN with the provided information. 108 * 109 * @param attributeName The attribute name for this RDN. It must not be 110 * {@code null}. 111 * @param attributeValue The attribute value for this RDN. It must not be 112 * {@code null}. 113 * @param schema The schema to use to generate the normalized string 114 * representation of this RDN. It may be {@code null} 115 * if no schema is available. 116 */ 117 public RDN(final String attributeName, final String attributeValue, 118 final Schema schema) 119 { 120 Validator.ensureNotNull(attributeName, attributeValue); 121 122 this.schema = schema; 123 124 attributeNames = new String[] { attributeName }; 125 attributeValues = 126 new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 127 128 nameValuePairs = null; 129 normalizedString = null; 130 rdnString = null; 131 } 132 133 134 135 /** 136 * Creates a new single-valued RDN with the provided information. 137 * 138 * @param attributeName The attribute name for this RDN. It must not be 139 * {@code null}. 140 * @param attributeValue The attribute value for this RDN. It must not be 141 * {@code null}. 142 */ 143 public RDN(final String attributeName, final byte[] attributeValue) 144 { 145 this(attributeName, attributeValue, null); 146 } 147 148 149 150 /** 151 * Creates a new single-valued RDN with the provided information. 152 * 153 * @param attributeName The attribute name for this RDN. It must not be 154 * {@code null}. 155 * @param attributeValue The attribute value for this RDN. It must not be 156 * {@code null}. 157 * @param schema The schema to use to generate the normalized string 158 * representation of this RDN. It may be {@code null} 159 * if no schema is available. 160 */ 161 public RDN(final String attributeName, final byte[] attributeValue, 162 final Schema schema) 163 { 164 Validator.ensureNotNull(attributeName, attributeValue); 165 166 this.schema = schema; 167 168 attributeNames = new String[] { attributeName }; 169 attributeValues = 170 new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 171 172 nameValuePairs = null; 173 normalizedString = null; 174 rdnString = null; 175 } 176 177 178 179 /** 180 * Creates a new (potentially multivalued) RDN. The set of names must have 181 * the same number of elements as the set of values, and there must be at 182 * least one element in each array. 183 * 184 * @param attributeNames The set of attribute names for this RDN. It must 185 * not be {@code null} or empty. 186 * @param attributeValues The set of attribute values for this RDN. It must 187 * not be {@code null} or empty. 188 */ 189 public RDN(final String[] attributeNames, final String[] attributeValues) 190 { 191 this(attributeNames, attributeValues, null); 192 } 193 194 195 196 /** 197 * Creates a new (potentially multivalued) RDN. The set of names must have 198 * the same number of elements as the set of values, and there must be at 199 * least one element in each array. 200 * 201 * @param attributeNames The set of attribute names for this RDN. It must 202 * not be {@code null} or empty. 203 * @param attributeValues The set of attribute values for this RDN. It must 204 * not be {@code null} or empty. 205 * @param schema The schema to use to generate the normalized 206 * string representation of this RDN. It may be 207 * {@code null} if no schema is available. 208 */ 209 public RDN(final String[] attributeNames, final String[] attributeValues, 210 final Schema schema) 211 { 212 Validator.ensureNotNull(attributeNames, attributeValues); 213 Validator.ensureTrue(attributeNames.length == attributeValues.length, 214 "RDN.attributeNames and attributeValues must be the same size."); 215 Validator.ensureTrue(attributeNames.length > 0, 216 "RDN.attributeNames must not be empty."); 217 218 this.attributeNames = attributeNames; 219 this.schema = schema; 220 221 this.attributeValues = new ASN1OctetString[attributeValues.length]; 222 for (int i=0; i < attributeValues.length; i++) 223 { 224 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]); 225 } 226 227 nameValuePairs = null; 228 normalizedString = null; 229 rdnString = null; 230 } 231 232 233 234 /** 235 * Creates a new (potentially multivalued) RDN. The set of names must have 236 * the same number of elements as the set of values, and there must be at 237 * least one element in each array. 238 * 239 * @param attributeNames The set of attribute names for this RDN. It must 240 * not be {@code null} or empty. 241 * @param attributeValues The set of attribute values for this RDN. It must 242 * not be {@code null} or empty. 243 */ 244 public RDN(final String[] attributeNames, final byte[][] attributeValues) 245 { 246 this(attributeNames, attributeValues, null); 247 } 248 249 250 251 /** 252 * Creates a new (potentially multivalued) RDN. The set of names must have 253 * the same number of elements as the set of values, and there must be at 254 * least one element in each array. 255 * 256 * @param attributeNames The set of attribute names for this RDN. It must 257 * not be {@code null} or empty. 258 * @param attributeValues The set of attribute values for this RDN. It must 259 * not be {@code null} or empty. 260 * @param schema The schema to use to generate the normalized 261 * string representation of this RDN. It may be 262 * {@code null} if no schema is available. 263 */ 264 public RDN(final String[] attributeNames, final byte[][] attributeValues, 265 final Schema schema) 266 { 267 Validator.ensureNotNull(attributeNames, attributeValues); 268 Validator.ensureTrue(attributeNames.length == attributeValues.length, 269 "RDN.attributeNames and attributeValues must be the same size."); 270 Validator.ensureTrue(attributeNames.length > 0, 271 "RDN.attributeNames must not be empty."); 272 273 this.attributeNames = attributeNames; 274 this.schema = schema; 275 276 this.attributeValues = new ASN1OctetString[attributeValues.length]; 277 for (int i=0; i < attributeValues.length; i++) 278 { 279 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]); 280 } 281 282 nameValuePairs = null; 283 normalizedString = null; 284 rdnString = null; 285 } 286 287 288 289 /** 290 * Creates a new single-valued RDN with the provided information. 291 * 292 * @param attributeName The name to use for this RDN. 293 * @param attributeValue The value to use for this RDN. 294 * @param schema The schema to use to generate the normalized string 295 * representation of this RDN. It may be {@code null} 296 * if no schema is available. 297 * @param rdnString The string representation for this RDN. 298 */ 299 RDN(final String attributeName, final ASN1OctetString attributeValue, 300 final Schema schema, final String rdnString) 301 { 302 this.rdnString = rdnString; 303 this.schema = schema; 304 305 attributeNames = new String[] { attributeName }; 306 attributeValues = new ASN1OctetString[] { attributeValue }; 307 308 nameValuePairs = null; 309 normalizedString = null; 310 } 311 312 313 314 /** 315 * Creates a new potentially multivalued RDN with the provided information. 316 * 317 * @param attributeNames The set of names to use for this RDN. 318 * @param attributeValues The set of values to use for this RDN. 319 * @param rdnString The string representation for this RDN. 320 * @param schema The schema to use to generate the normalized 321 * string representation of this RDN. It may be 322 * {@code null} if no schema is available. 323 */ 324 RDN(final String[] attributeNames, final ASN1OctetString[] attributeValues, 325 final Schema schema, final String rdnString) 326 { 327 this.rdnString = rdnString; 328 this.schema = schema; 329 330 this.attributeNames = attributeNames; 331 this.attributeValues = attributeValues; 332 333 nameValuePairs = null; 334 normalizedString = null; 335 } 336 337 338 339 /** 340 * Creates a new RDN from the provided string representation. 341 * 342 * @param rdnString The string representation to use for this RDN. It must 343 * not be empty or {@code null}. 344 * 345 * @throws LDAPException If the provided string cannot be parsed as a valid 346 * RDN. 347 */ 348 public RDN(final String rdnString) 349 throws LDAPException 350 { 351 this(rdnString, (Schema) null, false); 352 } 353 354 355 356 /** 357 * Creates a new RDN from the provided string representation. 358 * 359 * @param rdnString The string representation to use for this RDN. It must 360 * not be empty or {@code null}. 361 * @param schema The schema to use to generate the normalized string 362 * representation of this RDN. It may be {@code null} if 363 * no schema is available. 364 * 365 * @throws LDAPException If the provided string cannot be parsed as a valid 366 * RDN. 367 */ 368 public RDN(final String rdnString, final Schema schema) 369 throws LDAPException 370 { 371 this(rdnString, schema, false); 372 } 373 374 375 376 /** 377 * Creates a new RDN from the provided string representation. 378 * 379 * @param rdnString The string representation to use for this RDN. 380 * It must not be empty or {@code null}. 381 * @param schema The schema to use to generate the normalized 382 * string representation of this RDN. It may be 383 * {@code null} if no schema is available. 384 * @param strictNameChecking Indicates whether to verify that all attribute 385 * type names are valid as per RFC 4514. If this 386 * is {@code false}, then some technically invalid 387 * characters may be accepted in attribute type 388 * names. If this is {@code true}, then names 389 * must be strictly compliant. 390 * 391 * @throws LDAPException If the provided string cannot be parsed as a valid 392 * RDN. 393 */ 394 public RDN(final String rdnString, final Schema schema, 395 final boolean strictNameChecking) 396 throws LDAPException 397 { 398 Validator.ensureNotNull(rdnString); 399 400 this.rdnString = rdnString; 401 this.schema = schema; 402 403 nameValuePairs = null; 404 normalizedString = null; 405 406 int pos = 0; 407 final int length = rdnString.length(); 408 409 // First, skip over any leading spaces. 410 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 411 { 412 pos++; 413 } 414 415 // Read until we find a space or an equal sign. 416 int attrStartPos = pos; 417 while (pos < length) 418 { 419 final char c = rdnString.charAt(pos); 420 if ((c == ' ') || (c == '=')) 421 { 422 break; 423 } 424 425 pos++; 426 } 427 428 // Extract the attribute name, and optionally verify that it is valid. 429 String attrName = rdnString.substring(attrStartPos, pos); 430 if (attrName.isEmpty()) 431 { 432 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 433 ERR_RDN_NO_ATTR_NAME.get(rdnString)); 434 } 435 436 if (strictNameChecking) 437 { 438 if (! (Attribute.nameIsValid(attrName) || 439 StaticUtils.isNumericOID(attrName))) 440 { 441 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 442 ERR_RDN_INVALID_ATTR_NAME.get(rdnString, attrName)); 443 } 444 } 445 446 447 // Skip over any spaces between the attribute name and the equal sign. 448 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 449 { 450 pos++; 451 } 452 453 if ((pos >= length) || (rdnString.charAt(pos) != '=')) 454 { 455 // We didn't find an equal sign. 456 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 457 ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName)); 458 } 459 460 461 // The next character is the equal sign. Skip it, and then skip over any 462 // spaces between it and the attribute value. 463 pos++; 464 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 465 { 466 pos++; 467 } 468 469 470 // Look at the next character. If it is an octothorpe (#), then the value 471 // must be a hex-encoded BER element, which we'll need to parse and take the 472 // value of that element. Otherwise, it's a regular string (although 473 // possibly containing escaped or quoted characters). 474 ASN1OctetString value; 475 if (pos >= length) 476 { 477 value = new ASN1OctetString(); 478 } 479 else if (rdnString.charAt(pos) == '#') 480 { 481 // It is a hex-encoded value, so we'll read until we find the end of the 482 // string or the first non-hex character, which must be either a space or 483 // a plus sign. 484 final byte[] valueArray = readHexString(rdnString, ++pos); 485 486 try 487 { 488 value = ASN1OctetString.decodeAsOctetString(valueArray); 489 } 490 catch (final Exception e) 491 { 492 Debug.debugException(e); 493 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 494 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e); 495 } 496 497 pos += (valueArray.length * 2); 498 } 499 else 500 { 501 // It is a string value, which potentially includes escaped characters. 502 final StringBuilder buffer = new StringBuilder(); 503 pos = readValueString(rdnString, pos, buffer); 504 value = new ASN1OctetString(buffer.toString()); 505 } 506 507 508 // Skip over any spaces until we find a plus sign or the end of the value. 509 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 510 { 511 pos++; 512 } 513 514 if (pos >= length) 515 { 516 // It's a single-valued RDN, so we have everything that we need. 517 attributeNames = new String[] { attrName }; 518 attributeValues = new ASN1OctetString[] { value }; 519 return; 520 } 521 522 // It's a multivalued RDN, so create temporary lists to hold the names and 523 // values. 524 final ArrayList<String> nameList = new ArrayList<>(5); 525 final ArrayList<ASN1OctetString> valueList = new ArrayList<>(5); 526 nameList.add(attrName); 527 valueList.add(value); 528 529 if (rdnString.charAt(pos) == '+') 530 { 531 pos++; 532 } 533 else 534 { 535 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 536 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 537 } 538 539 if (pos >= length) 540 { 541 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 542 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 543 } 544 545 int numValues = 1; 546 while (pos < length) 547 { 548 // Skip over any spaces between the plus sign and the attribute name. 549 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 550 { 551 pos++; 552 } 553 554 attrStartPos = pos; 555 while (pos < length) 556 { 557 final char c = rdnString.charAt(pos); 558 if ((c == ' ') || (c == '=')) 559 { 560 break; 561 } 562 563 pos++; 564 } 565 566 // Extract and validate the attribute type name. 567 attrName = rdnString.substring(attrStartPos, pos); 568 if (attrName.isEmpty()) 569 { 570 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 571 ERR_RDN_NO_ATTR_NAME.get(rdnString)); 572 } 573 574 if (strictNameChecking) 575 { 576 if (! (Attribute.nameIsValid(attrName) || 577 StaticUtils.isNumericOID(attrName))) 578 { 579 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 580 ERR_RDN_INVALID_ATTR_NAME.get(rdnString, attrName)); 581 } 582 } 583 584 // Skip over any spaces between the attribute name and the equal sign. 585 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 586 { 587 pos++; 588 } 589 590 if ((pos >= length) || (rdnString.charAt(pos) != '=')) 591 { 592 // We didn't find an equal sign. 593 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 594 ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName)); 595 } 596 597 // The next character is the equal sign. Skip it, and then skip over any 598 // spaces between it and the attribute value. 599 pos++; 600 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 601 { 602 pos++; 603 } 604 605 // Look at the next character. If it is an octothorpe (#), then the value 606 // must be a hex-encoded BER element, which we'll need to parse and take 607 // the value of that element. Otherwise, it's a regular string (although 608 // possibly containing escaped or quoted characters). 609 if (pos >= length) 610 { 611 value = new ASN1OctetString(); 612 } 613 else if (rdnString.charAt(pos) == '#') 614 { 615 // It is a hex-encoded value, so we'll read until we find the end of the 616 // string or the first non-hex character, which must be either a space 617 // or a plus sign. 618 final byte[] valueArray = readHexString(rdnString, ++pos); 619 620 try 621 { 622 value = ASN1OctetString.decodeAsOctetString(valueArray); 623 } 624 catch (final Exception e) 625 { 626 Debug.debugException(e); 627 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 628 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e); 629 } 630 631 pos += (valueArray.length * 2); 632 } 633 else 634 { 635 // It is a string value, which potentially includes escaped characters. 636 final StringBuilder buffer = new StringBuilder(); 637 pos = readValueString(rdnString, pos, buffer); 638 value = new ASN1OctetString(buffer.toString()); 639 } 640 641 642 // Skip over any spaces until we find a plus sign or the end of the value. 643 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 644 { 645 pos++; 646 } 647 648 nameList.add(attrName); 649 valueList.add(value); 650 numValues++; 651 652 if (pos >= length) 653 { 654 // We're at the end of the value, so break out of the loop. 655 break; 656 } 657 else 658 { 659 // Skip over the plus sign and loop again to read another name-value 660 // pair. 661 if (rdnString.charAt(pos) == '+') 662 { 663 pos++; 664 } 665 else 666 { 667 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 668 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 669 } 670 } 671 672 if (pos >= length) 673 { 674 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 675 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 676 } 677 } 678 679 attributeNames = new String[numValues]; 680 attributeValues = new ASN1OctetString[numValues]; 681 for (int i=0; i < numValues; i++) 682 { 683 attributeNames[i] = nameList.get(i); 684 attributeValues[i] = valueList.get(i); 685 } 686 } 687 688 689 690 /** 691 * Parses a hex-encoded RDN value from the provided string. Reading will 692 * continue until the end of the string is reached or a non-escaped plus sign 693 * is encountered. After returning, the caller should increment its position 694 * by two times the length of the value array. 695 * 696 * @param rdnString The string to be parsed. It must not be {@code null}. 697 * @param startPos The position at which to start reading the value. It 698 * should be the position immediately after the octothorpe 699 * at the start of the hex-encoded value. 700 * 701 * @return A byte array containing the parsed value. 702 * 703 * @throws LDAPException If an error occurs while reading the value (e.g., 704 * if it contains non-hex characters, or has an odd 705 * number of characters. 706 */ 707 static byte[] readHexString(final String rdnString, final int startPos) 708 throws LDAPException 709 { 710 final int length = rdnString.length(); 711 int pos = startPos; 712 713 final ByteBuffer buffer = ByteBuffer.allocate(length-pos); 714hexLoop: 715 while (pos < length) 716 { 717 final byte hexByte; 718 switch (rdnString.charAt(pos++)) 719 { 720 case '0': 721 hexByte = 0x00; 722 break; 723 case '1': 724 hexByte = 0x10; 725 break; 726 case '2': 727 hexByte = 0x20; 728 break; 729 case '3': 730 hexByte = 0x30; 731 break; 732 case '4': 733 hexByte = 0x40; 734 break; 735 case '5': 736 hexByte = 0x50; 737 break; 738 case '6': 739 hexByte = 0x60; 740 break; 741 case '7': 742 hexByte = 0x70; 743 break; 744 case '8': 745 hexByte = (byte) 0x80; 746 break; 747 case '9': 748 hexByte = (byte) 0x90; 749 break; 750 case 'a': 751 case 'A': 752 hexByte = (byte) 0xA0; 753 break; 754 case 'b': 755 case 'B': 756 hexByte = (byte) 0xB0; 757 break; 758 case 'c': 759 case 'C': 760 hexByte = (byte) 0xC0; 761 break; 762 case 'd': 763 case 'D': 764 hexByte = (byte) 0xD0; 765 break; 766 case 'e': 767 case 'E': 768 hexByte = (byte) 0xE0; 769 break; 770 case 'f': 771 case 'F': 772 hexByte = (byte) 0xF0; 773 break; 774 case ' ': 775 case '+': 776 case ',': 777 case ';': 778 // This indicates that we've reached the end of the hex string. 779 break hexLoop; 780 default: 781 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 782 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 783 (pos-1))); 784 } 785 786 if (pos >= length) 787 { 788 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 789 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 790 } 791 792 switch (rdnString.charAt(pos++)) 793 { 794 case '0': 795 buffer.put(hexByte); 796 break; 797 case '1': 798 buffer.put((byte) (hexByte | 0x01)); 799 break; 800 case '2': 801 buffer.put((byte) (hexByte | 0x02)); 802 break; 803 case '3': 804 buffer.put((byte) (hexByte | 0x03)); 805 break; 806 case '4': 807 buffer.put((byte) (hexByte | 0x04)); 808 break; 809 case '5': 810 buffer.put((byte) (hexByte | 0x05)); 811 break; 812 case '6': 813 buffer.put((byte) (hexByte | 0x06)); 814 break; 815 case '7': 816 buffer.put((byte) (hexByte | 0x07)); 817 break; 818 case '8': 819 buffer.put((byte) (hexByte | 0x08)); 820 break; 821 case '9': 822 buffer.put((byte) (hexByte | 0x09)); 823 break; 824 case 'a': 825 case 'A': 826 buffer.put((byte) (hexByte | 0x0A)); 827 break; 828 case 'b': 829 case 'B': 830 buffer.put((byte) (hexByte | 0x0B)); 831 break; 832 case 'c': 833 case 'C': 834 buffer.put((byte) (hexByte | 0x0C)); 835 break; 836 case 'd': 837 case 'D': 838 buffer.put((byte) (hexByte | 0x0D)); 839 break; 840 case 'e': 841 case 'E': 842 buffer.put((byte) (hexByte | 0x0E)); 843 break; 844 case 'f': 845 case 'F': 846 buffer.put((byte) (hexByte | 0x0F)); 847 break; 848 default: 849 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 850 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 851 (pos-1))); 852 } 853 } 854 855 buffer.flip(); 856 final byte[] valueArray = new byte[buffer.limit()]; 857 buffer.get(valueArray); 858 return valueArray; 859 } 860 861 862 863 /** 864 * Reads a string value from the provided RDN string. Reading will continue 865 * until the end of the string is reached or until a non-escaped plus sign is 866 * encountered. 867 * 868 * @param rdnString The string from which to read the value. 869 * @param startPos The position in the RDN string at which to start reading 870 * the value. 871 * @param buffer The buffer into which the parsed value should be 872 * placed. 873 * 874 * @return The position at which the caller should continue reading when 875 * parsing the RDN. 876 * 877 * @throws LDAPException If a problem occurs while reading the value. 878 */ 879 static int readValueString(final String rdnString, final int startPos, 880 final StringBuilder buffer) 881 throws LDAPException 882 { 883 final int length = rdnString.length(); 884 int pos = startPos; 885 886 boolean inQuotes = false; 887valueLoop: 888 while (pos < length) 889 { 890 char c = rdnString.charAt(pos); 891 switch (c) 892 { 893 case '\\': 894 // It's an escaped value. It can either be followed by a single 895 // character (e.g., backslash, space, octothorpe, equals, double 896 // quote, plus sign, comma, semicolon, less than, or greater-than), or 897 // two hex digits. If it is followed by hex digits, then continue 898 // reading to see if there are more of them. 899 if ((pos+1) >= length) 900 { 901 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 902 ERR_RDN_ENDS_WITH_BACKSLASH.get(rdnString)); 903 } 904 else 905 { 906 pos++; 907 c = rdnString.charAt(pos); 908 if (StaticUtils.isHex(c)) 909 { 910 // We need to subtract one from the resulting position because 911 // it will be incremented later. 912 pos = readEscapedHexString(rdnString, pos, buffer) - 1; 913 } 914 else 915 { 916 buffer.append(c); 917 } 918 } 919 break; 920 921 case '"': 922 if (inQuotes) 923 { 924 // This should be the end of the value. If it's not, then fail. 925 pos++; 926 while (pos < length) 927 { 928 c = rdnString.charAt(pos); 929 if ((c == '+') || (c == ',') || (c == ';')) 930 { 931 break; 932 } 933 else if (c != ' ') 934 { 935 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 936 ERR_RDN_CHAR_OUTSIDE_QUOTES.get(rdnString, c, (pos-1))); 937 } 938 939 pos++; 940 } 941 942 inQuotes = false; 943 break valueLoop; 944 } 945 else 946 { 947 // This should be the first character of the value. 948 if (pos == startPos) 949 { 950 inQuotes = true; 951 } 952 else 953 { 954 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 955 ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(rdnString, pos)); 956 } 957 } 958 break; 959 960 case ',': 961 case ';': 962 case '+': 963 // This denotes the end of the value, if it's not in quotes. 964 if (inQuotes) 965 { 966 buffer.append(c); 967 } 968 else 969 { 970 break valueLoop; 971 } 972 break; 973 974 default: 975 // This is a normal character that should be added to the buffer. 976 buffer.append(c); 977 break; 978 } 979 980 pos++; 981 } 982 983 984 // If the value started with a quotation mark, then make sure it was closed. 985 if (inQuotes) 986 { 987 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 988 ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get(rdnString)); 989 } 990 991 992 // If the value ends with any unescaped trailing spaces, then trim them off. 993 int bufferPos = buffer.length() - 1; 994 int rdnStrPos = pos - 2; 995 while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' ')) 996 { 997 if (rdnString.charAt(rdnStrPos) == '\\') 998 { 999 break; 1000 } 1001 else 1002 { 1003 buffer.deleteCharAt(bufferPos--); 1004 rdnStrPos--; 1005 } 1006 } 1007 1008 return pos; 1009 } 1010 1011 1012 1013 /** 1014 * Reads one or more hex-encoded bytes from the specified portion of the RDN 1015 * string. 1016 * 1017 * @param rdnString The string from which the data is to be read. 1018 * @param startPos The position at which to start reading. This should be 1019 * the first hex character immediately after the initial 1020 * backslash. 1021 * @param buffer The buffer to which the decoded string portion should be 1022 * appended. 1023 * 1024 * @return The position at which the caller may resume parsing. 1025 * 1026 * @throws LDAPException If a problem occurs while reading hex-encoded 1027 * bytes. 1028 */ 1029 private static int readEscapedHexString(final String rdnString, 1030 final int startPos, 1031 final StringBuilder buffer) 1032 throws LDAPException 1033 { 1034 final int length = rdnString.length(); 1035 int pos = startPos; 1036 1037 final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos); 1038 while (pos < length) 1039 { 1040 final byte b; 1041 switch (rdnString.charAt(pos++)) 1042 { 1043 case '0': 1044 b = 0x00; 1045 break; 1046 case '1': 1047 b = 0x10; 1048 break; 1049 case '2': 1050 b = 0x20; 1051 break; 1052 case '3': 1053 b = 0x30; 1054 break; 1055 case '4': 1056 b = 0x40; 1057 break; 1058 case '5': 1059 b = 0x50; 1060 break; 1061 case '6': 1062 b = 0x60; 1063 break; 1064 case '7': 1065 b = 0x70; 1066 break; 1067 case '8': 1068 b = (byte) 0x80; 1069 break; 1070 case '9': 1071 b = (byte) 0x90; 1072 break; 1073 case 'a': 1074 case 'A': 1075 b = (byte) 0xA0; 1076 break; 1077 case 'b': 1078 case 'B': 1079 b = (byte) 0xB0; 1080 break; 1081 case 'c': 1082 case 'C': 1083 b = (byte) 0xC0; 1084 break; 1085 case 'd': 1086 case 'D': 1087 b = (byte) 0xD0; 1088 break; 1089 case 'e': 1090 case 'E': 1091 b = (byte) 0xE0; 1092 break; 1093 case 'f': 1094 case 'F': 1095 b = (byte) 0xF0; 1096 break; 1097 default: 1098 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1099 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1100 (pos-1))); 1101 } 1102 1103 if (pos >= length) 1104 { 1105 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1106 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 1107 } 1108 1109 switch (rdnString.charAt(pos++)) 1110 { 1111 case '0': 1112 byteBuffer.put(b); 1113 break; 1114 case '1': 1115 byteBuffer.put((byte) (b | 0x01)); 1116 break; 1117 case '2': 1118 byteBuffer.put((byte) (b | 0x02)); 1119 break; 1120 case '3': 1121 byteBuffer.put((byte) (b | 0x03)); 1122 break; 1123 case '4': 1124 byteBuffer.put((byte) (b | 0x04)); 1125 break; 1126 case '5': 1127 byteBuffer.put((byte) (b | 0x05)); 1128 break; 1129 case '6': 1130 byteBuffer.put((byte) (b | 0x06)); 1131 break; 1132 case '7': 1133 byteBuffer.put((byte) (b | 0x07)); 1134 break; 1135 case '8': 1136 byteBuffer.put((byte) (b | 0x08)); 1137 break; 1138 case '9': 1139 byteBuffer.put((byte) (b | 0x09)); 1140 break; 1141 case 'a': 1142 case 'A': 1143 byteBuffer.put((byte) (b | 0x0A)); 1144 break; 1145 case 'b': 1146 case 'B': 1147 byteBuffer.put((byte) (b | 0x0B)); 1148 break; 1149 case 'c': 1150 case 'C': 1151 byteBuffer.put((byte) (b | 0x0C)); 1152 break; 1153 case 'd': 1154 case 'D': 1155 byteBuffer.put((byte) (b | 0x0D)); 1156 break; 1157 case 'e': 1158 case 'E': 1159 byteBuffer.put((byte) (b | 0x0E)); 1160 break; 1161 case 'f': 1162 case 'F': 1163 byteBuffer.put((byte) (b | 0x0F)); 1164 break; 1165 default: 1166 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1167 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1168 (pos-1))); 1169 } 1170 1171 if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') && 1172 StaticUtils.isHex(rdnString.charAt(pos+1))) 1173 { 1174 // It appears that there are more hex-encoded bytes to follow, so keep 1175 // reading. 1176 pos++; 1177 continue; 1178 } 1179 else 1180 { 1181 break; 1182 } 1183 } 1184 1185 byteBuffer.flip(); 1186 final byte[] byteArray = new byte[byteBuffer.limit()]; 1187 byteBuffer.get(byteArray); 1188 1189 try 1190 { 1191 buffer.append(StaticUtils.toUTF8String(byteArray)); 1192 } 1193 catch (final Exception e) 1194 { 1195 Debug.debugException(e); 1196 // This should never happen. 1197 buffer.append(new String(byteArray)); 1198 } 1199 1200 return pos; 1201 } 1202 1203 1204 1205 /** 1206 * Indicates whether the provided string represents a valid RDN. 1207 * 1208 * @param s The string for which to make the determination. It must not be 1209 * {@code null}. 1210 * 1211 * @return {@code true} if the provided string represents a valid RDN, or 1212 * {@code false} if not. 1213 */ 1214 public static boolean isValidRDN(final String s) 1215 { 1216 return isValidRDN(s, false); 1217 } 1218 1219 1220 1221 /** 1222 * Indicates whether the provided string represents a valid RDN. 1223 * 1224 * @param s The string for which to make the determination. 1225 * It must not be {@code null}. 1226 * @param strictNameChecking Indicates whether to verify that all attribute 1227 * type names are valid as per RFC 4514. If this 1228 * is {@code false}, then some technically invalid 1229 * characters may be accepted in attribute type 1230 * names. If this is {@code true}, then names 1231 * must be strictly compliant. 1232 * 1233 * @return {@code true} if the provided string represents a valid RDN, or 1234 * {@code false} if not. 1235 */ 1236 public static boolean isValidRDN(final String s, 1237 final boolean strictNameChecking) 1238 { 1239 try 1240 { 1241 new RDN(s, null, strictNameChecking); 1242 return true; 1243 } 1244 catch (final LDAPException le) 1245 { 1246 Debug.debugException(le); 1247 return false; 1248 } 1249 } 1250 1251 1252 1253 /** 1254 * Indicates whether this RDN contains multiple values. 1255 * 1256 * @return {@code true} if this RDN contains multiple values, or 1257 * {@code false} if not. 1258 */ 1259 public boolean isMultiValued() 1260 { 1261 return (attributeNames.length != 1); 1262 } 1263 1264 1265 1266 /** 1267 * Retrieves the number of values for this RDN. 1268 * 1269 * @return The number of values for this RDN. 1270 */ 1271 public int getValueCount() 1272 { 1273 return attributeNames.length; 1274 } 1275 1276 1277 1278 /** 1279 * Retrieves an array of the attributes that comprise this RDN. 1280 * 1281 * @return An array of the attributes that comprise this RDN. 1282 */ 1283 public Attribute[] getAttributes() 1284 { 1285 final Attribute[] attrs = new Attribute[attributeNames.length]; 1286 for (int i=0; i < attrs.length; i++) 1287 { 1288 attrs[i] = new Attribute(attributeNames[i], schema, 1289 new ASN1OctetString[] { attributeValues[i] }); 1290 } 1291 1292 return attrs; 1293 } 1294 1295 1296 1297 /** 1298 * Retrieves the set of attribute names for this RDN. 1299 * 1300 * @return The set of attribute names for this RDN. 1301 */ 1302 public String[] getAttributeNames() 1303 { 1304 return attributeNames; 1305 } 1306 1307 1308 1309 /** 1310 * Retrieves the set of attribute values for this RDN. 1311 * 1312 * @return The set of attribute values for this RDN. 1313 */ 1314 public String[] getAttributeValues() 1315 { 1316 final String[] stringValues = new String[attributeValues.length]; 1317 for (int i=0; i < stringValues.length; i++) 1318 { 1319 stringValues[i] = attributeValues[i].stringValue(); 1320 } 1321 1322 return stringValues; 1323 } 1324 1325 1326 1327 /** 1328 * Retrieves the set of attribute values for this RDN. 1329 * 1330 * @return The set of attribute values for this RDN. 1331 */ 1332 public byte[][] getByteArrayAttributeValues() 1333 { 1334 final byte[][] byteValues = new byte[attributeValues.length][]; 1335 for (int i=0; i < byteValues.length; i++) 1336 { 1337 byteValues[i] = attributeValues[i].getValue(); 1338 } 1339 1340 return byteValues; 1341 } 1342 1343 1344 1345 /** 1346 * Retrieves a sorted set of the name-value pairs that comprise this RDN. 1347 * 1348 * @return A sorted set of the name-value pairs that comprise this RDN. 1349 */ 1350 SortedSet<RDNNameValuePair> getNameValuePairs() 1351 { 1352 if (nameValuePairs == null) 1353 { 1354 final SortedSet<RDNNameValuePair> s = new TreeSet<>(); 1355 for (int i=0; i < attributeNames.length; i++) 1356 { 1357 s.add(new RDNNameValuePair(attributeNames[i], attributeValues[i], 1358 schema)); 1359 } 1360 1361 nameValuePairs = Collections.unmodifiableSortedSet(s); 1362 } 1363 1364 return nameValuePairs; 1365 } 1366 1367 1368 1369 /** 1370 * Retrieves the schema that will be used for this RDN, if any. 1371 * 1372 * @return The schema that will be used for this RDN, or {@code null} if none 1373 * has been provided. 1374 */ 1375 Schema getSchema() 1376 { 1377 return schema; 1378 } 1379 1380 1381 1382 /** 1383 * Indicates whether this RDN contains the specified attribute. 1384 * 1385 * @param attributeName The name of the attribute for which to make the 1386 * determination. 1387 * 1388 * @return {@code true} if RDN contains the specified attribute, or 1389 * {@code false} if not. 1390 */ 1391 public boolean hasAttribute(final String attributeName) 1392 { 1393 for (final String name : attributeNames) 1394 { 1395 if (name.equalsIgnoreCase(attributeName)) 1396 { 1397 return true; 1398 } 1399 } 1400 1401 return false; 1402 } 1403 1404 1405 1406 /** 1407 * Indicates whether this RDN contains the specified attribute value. 1408 * 1409 * @param attributeName The name of the attribute for which to make the 1410 * determination. 1411 * @param attributeValue The attribute value for which to make the 1412 * determination. 1413 * 1414 * @return {@code true} if RDN contains the specified attribute, or 1415 * {@code false} if not. 1416 */ 1417 public boolean hasAttributeValue(final String attributeName, 1418 final String attributeValue) 1419 { 1420 for (int i=0; i < attributeNames.length; i++) 1421 { 1422 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1423 { 1424 final Attribute a = 1425 new Attribute(attributeName, schema, attributeValue); 1426 final Attribute b = new Attribute(attributeName, schema, 1427 attributeValues[i].stringValue()); 1428 1429 if (a.equals(b)) 1430 { 1431 return true; 1432 } 1433 } 1434 } 1435 1436 return false; 1437 } 1438 1439 1440 1441 /** 1442 * Indicates whether this RDN contains the specified attribute value. 1443 * 1444 * @param attributeName The name of the attribute for which to make the 1445 * determination. 1446 * @param attributeValue The attribute value for which to make the 1447 * determination. 1448 * 1449 * @return {@code true} if RDN contains the specified attribute, or 1450 * {@code false} if not. 1451 */ 1452 public boolean hasAttributeValue(final String attributeName, 1453 final byte[] attributeValue) 1454 { 1455 for (int i=0; i < attributeNames.length; i++) 1456 { 1457 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1458 { 1459 final Attribute a = 1460 new Attribute(attributeName, schema, attributeValue); 1461 final Attribute b = new Attribute(attributeName, schema, 1462 attributeValues[i].getValue()); 1463 1464 if (a.equals(b)) 1465 { 1466 return true; 1467 } 1468 } 1469 } 1470 1471 return false; 1472 } 1473 1474 1475 1476 /** 1477 * Retrieves a string representation of this RDN. 1478 * 1479 * @return A string representation of this RDN. 1480 */ 1481 @Override() 1482 public String toString() 1483 { 1484 if (rdnString == null) 1485 { 1486 final StringBuilder buffer = new StringBuilder(); 1487 toString(buffer, false); 1488 rdnString = buffer.toString(); 1489 } 1490 1491 return rdnString; 1492 } 1493 1494 1495 1496 /** 1497 * Retrieves a string representation of this RDN with minimal encoding for 1498 * special characters. Only those characters specified in RFC 4514 section 1499 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or 1500 * non-printable ASCII characters. 1501 * 1502 * @return A string representation of this RDN with minimal encoding for 1503 * special characters. 1504 */ 1505 public String toMinimallyEncodedString() 1506 { 1507 final StringBuilder buffer = new StringBuilder(); 1508 toString(buffer, true); 1509 return buffer.toString(); 1510 } 1511 1512 1513 1514 /** 1515 * Appends a string representation of this RDN to the provided buffer. 1516 * 1517 * @param buffer The buffer to which the string representation is to be 1518 * appended. 1519 */ 1520 public void toString(final StringBuilder buffer) 1521 { 1522 toString(buffer, false); 1523 } 1524 1525 1526 1527 /** 1528 * Appends a string representation of this RDN to the provided buffer. 1529 * 1530 * @param buffer The buffer to which the string representation is 1531 * to be appended. 1532 * @param minimizeEncoding Indicates whether to restrict the encoding of 1533 * special characters to the bare minimum required 1534 * by LDAP (as per RFC 4514 section 2.4). If this 1535 * is {@code true}, then only leading and trailing 1536 * spaces, double quotes, plus signs, commas, 1537 * semicolons, greater-than, less-than, and 1538 * backslash characters will be encoded. 1539 */ 1540 public void toString(final StringBuilder buffer, 1541 final boolean minimizeEncoding) 1542 { 1543 if ((rdnString != null) && (! minimizeEncoding)) 1544 { 1545 buffer.append(rdnString); 1546 return; 1547 } 1548 1549 for (int i=0; i < attributeNames.length; i++) 1550 { 1551 if (i > 0) 1552 { 1553 buffer.append('+'); 1554 } 1555 1556 buffer.append(attributeNames[i]); 1557 buffer.append('='); 1558 appendValue(buffer, attributeValues[i], minimizeEncoding); 1559 } 1560 } 1561 1562 1563 1564 /** 1565 * Appends an appropriately escaped version of the provided value to the given 1566 * buffer. 1567 * 1568 * @param buffer The buffer to which the value should be appended. 1569 * It must not be {@code null}. 1570 * @param value The value to be appended in an appropriately 1571 * escaped form. It must not be {@code null}. 1572 * @param minimizeEncoding Indicates whether to restrict the encoding of 1573 * special characters to the bare minimum required 1574 * by LDAP (as per RFC 4514 section 2.4). If this 1575 * is {@code true}, then only leading and trailing 1576 * spaces, double quotes, plus signs, commas, 1577 * semicolons, greater-than, less-than, and 1578 * backslash characters will be encoded. 1579 */ 1580 static void appendValue(final StringBuilder buffer, 1581 final ASN1OctetString value, 1582 final boolean minimizeEncoding) 1583 { 1584 final String valueString = value.stringValue(); 1585 final int length = valueString.length(); 1586 for (int j=0; j < length; j++) 1587 { 1588 final char c = valueString.charAt(j); 1589 switch (c) 1590 { 1591 case '\\': 1592 case '=': 1593 case '"': 1594 case '+': 1595 case ',': 1596 case ';': 1597 case '<': 1598 case '>': 1599 // These characters will always be escaped. 1600 buffer.append('\\'); 1601 buffer.append(c); 1602 break; 1603 1604 case '#': 1605 // Escape the octothorpe only if it's the first character. 1606 if (j == 0) 1607 { 1608 buffer.append("\\#"); 1609 } 1610 else 1611 { 1612 buffer.append('#'); 1613 } 1614 break; 1615 1616 case ' ': 1617 // Escape this space only if it's the first or last character. 1618 if ((j == 0) || ((j+1) == length)) 1619 { 1620 buffer.append("\\ "); 1621 } 1622 else 1623 { 1624 buffer.append(' '); 1625 } 1626 break; 1627 1628 case '\u0000': 1629 buffer.append("\\00"); 1630 break; 1631 1632 default: 1633 // If it's not a printable ASCII character, then hex-encode it 1634 // unless we're using minimized encoding. 1635 if ((! minimizeEncoding) && ((c < ' ') || (c > '~'))) 1636 { 1637 StaticUtils.hexEncode(c, buffer); 1638 } 1639 else 1640 { 1641 buffer.append(c); 1642 } 1643 break; 1644 } 1645 } 1646 } 1647 1648 1649 1650 /** 1651 * Retrieves a normalized string representation of this RDN. 1652 * 1653 * @return A normalized string representation of this RDN. 1654 */ 1655 public String toNormalizedString() 1656 { 1657 if (normalizedString == null) 1658 { 1659 final StringBuilder buffer = new StringBuilder(); 1660 toNormalizedString(buffer); 1661 normalizedString = buffer.toString(); 1662 } 1663 1664 return normalizedString; 1665 } 1666 1667 1668 1669 /** 1670 * Appends a normalized string representation of this RDN to the provided 1671 * buffer. 1672 * 1673 * @param buffer The buffer to which the normalized string representation is 1674 * to be appended. 1675 */ 1676 public void toNormalizedString(final StringBuilder buffer) 1677 { 1678 if (attributeNames.length == 1) 1679 { 1680 // It's a single-valued RDN, so there is no need to sort anything. 1681 final String name = normalizeAttrName(attributeNames[0]); 1682 buffer.append(name); 1683 buffer.append('='); 1684 appendNormalizedValue(buffer, name, attributeValues[0], schema); 1685 } 1686 else 1687 { 1688 // It's a multivalued RDN, so we need to sort the components. 1689 final Iterator<RDNNameValuePair> iterator = 1690 getNameValuePairs().iterator(); 1691 while (iterator.hasNext()) 1692 { 1693 buffer.append(iterator.next().toNormalizedString()); 1694 if (iterator.hasNext()) 1695 { 1696 buffer.append('+'); 1697 } 1698 } 1699 } 1700 } 1701 1702 1703 1704 /** 1705 * Obtains a normalized representation of the provided attribute name. 1706 * 1707 * @param name The name of the attribute for which to create the normalized 1708 * representation. 1709 * 1710 * @return A normalized representation of the provided attribute name. 1711 */ 1712 private String normalizeAttrName(final String name) 1713 { 1714 String n = name; 1715 if (schema != null) 1716 { 1717 final AttributeTypeDefinition at = schema.getAttributeType(name); 1718 if (at != null) 1719 { 1720 n = at.getNameOrOID(); 1721 } 1722 } 1723 return StaticUtils.toLowerCase(n); 1724 } 1725 1726 1727 1728 /** 1729 * Retrieves a normalized string representation of the RDN with the provided 1730 * string representation. 1731 * 1732 * @param s The string representation of the RDN to normalize. It must not 1733 * be {@code null}. 1734 * 1735 * @return The normalized string representation of the RDN with the provided 1736 * string representation. 1737 * 1738 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1739 */ 1740 public static String normalize(final String s) 1741 throws LDAPException 1742 { 1743 return normalize(s, null); 1744 } 1745 1746 1747 1748 /** 1749 * Retrieves a normalized string representation of the RDN with the provided 1750 * string representation. 1751 * 1752 * @param s The string representation of the RDN to normalize. It must 1753 * not be {@code null}. 1754 * @param schema The schema to use to generate the normalized string 1755 * representation of the RDN. It may be {@code null} if no 1756 * schema is available. 1757 * 1758 * @return The normalized string representation of the RDN with the provided 1759 * string representation. 1760 * 1761 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1762 */ 1763 public static String normalize(final String s, final Schema schema) 1764 throws LDAPException 1765 { 1766 return new RDN(s, schema).toNormalizedString(); 1767 } 1768 1769 1770 1771 /** 1772 * Appends a normalized string representation of the provided attribute value 1773 * to the given buffer. 1774 * 1775 * @param buffer The buffer to which the value should be appended. 1776 * It must not be {@code null}. 1777 * @param attributeName The name of the attribute whose value is to be 1778 * normalized. It must not be {@code null}. 1779 * @param value The value to be normalized. It must not be 1780 * {@code null}. 1781 * @param schema The schema to use to generate the normalized 1782 * representation of the value. It may be {@code null} 1783 * if no schema is available. 1784 */ 1785 static void appendNormalizedValue(final StringBuilder buffer, 1786 final String attributeName, 1787 final ASN1OctetString value, 1788 final Schema schema) 1789 { 1790 final MatchingRule matchingRule = 1791 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 1792 1793 ASN1OctetString rawNormValue; 1794 try 1795 { 1796 rawNormValue = matchingRule.normalize(value); 1797 } 1798 catch (final Exception e) 1799 { 1800 Debug.debugException(e); 1801 rawNormValue = 1802 new ASN1OctetString(StaticUtils.toLowerCase(value.stringValue())); 1803 } 1804 1805 final String valueString = rawNormValue.stringValue(); 1806 final int length = valueString.length(); 1807 for (int i=0; i < length; i++) 1808 { 1809 final char c = valueString.charAt(i); 1810 1811 switch (c) 1812 { 1813 case '\\': 1814 case '=': 1815 case '"': 1816 case '+': 1817 case ',': 1818 case ';': 1819 case '<': 1820 case '>': 1821 buffer.append('\\'); 1822 buffer.append(c); 1823 break; 1824 1825 case '#': 1826 // Escape the octothorpe only if it's the first character. 1827 if (i == 0) 1828 { 1829 buffer.append("\\#"); 1830 } 1831 else 1832 { 1833 buffer.append('#'); 1834 } 1835 break; 1836 1837 case ' ': 1838 // Escape this space only if it's the first or last character. 1839 if ((i == 0) || ((i+1) == length)) 1840 { 1841 buffer.append("\\ "); 1842 } 1843 else 1844 { 1845 buffer.append(' '); 1846 } 1847 break; 1848 1849 default: 1850 // If it's a printable ASCII character that isn't covered by one of 1851 // the above options, then just append it to the buffer. Otherwise, 1852 // hex-encode all bytes that comprise its UTF-8 representation, which 1853 // might require special handling if it requires two Java characters 1854 // to encode the Unicode character. 1855 if ((c >= ' ') && (c <= '~')) 1856 { 1857 buffer.append(c); 1858 } 1859 else if (Character.isHighSurrogate(c)) 1860 { 1861 if (((i+1) < length) && 1862 Character.isLowSurrogate(valueString.charAt(i+1))) 1863 { 1864 final char c2 = valueString.charAt(++i); 1865 final int codePoint = Character.toCodePoint(c, c2); 1866 StaticUtils.hexEncode(codePoint, buffer); 1867 } 1868 else 1869 { 1870 // This should never happen. 1871 StaticUtils.hexEncode(c, buffer); 1872 } 1873 } 1874 else 1875 { 1876 StaticUtils.hexEncode(c, buffer); 1877 } 1878 break; 1879 } 1880 } 1881 } 1882 1883 1884 1885 /** 1886 * Retrieves a hash code for this RDN. 1887 * 1888 * @return The hash code for this RDN. 1889 */ 1890 @Override() 1891 public int hashCode() 1892 { 1893 return toNormalizedString().hashCode(); 1894 } 1895 1896 1897 1898 /** 1899 * Indicates whether this RDN is equal to the provided object. The given 1900 * object will only be considered equal to this RDN if it is also an RDN with 1901 * the same set of names and values. 1902 * 1903 * @param o The object for which to make the determination. 1904 * 1905 * @return {@code true} if the provided object can be considered equal to 1906 * this RDN, or {@code false} if not. 1907 */ 1908 @Override() 1909 public boolean equals(final Object o) 1910 { 1911 if (o == null) 1912 { 1913 return false; 1914 } 1915 1916 if (o == this) 1917 { 1918 return true; 1919 } 1920 1921 if (! (o instanceof RDN)) 1922 { 1923 return false; 1924 } 1925 1926 final RDN rdn = (RDN) o; 1927 return (toNormalizedString().equals(rdn.toNormalizedString())); 1928 } 1929 1930 1931 1932 /** 1933 * Indicates whether the RDN with the provided string representation is equal 1934 * to this RDN. 1935 * 1936 * @param s The string representation of the DN to compare with this RDN. 1937 * 1938 * @return {@code true} if the DN with the provided string representation is 1939 * equal to this RDN, or {@code false} if not. 1940 * 1941 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1942 */ 1943 public boolean equals(final String s) 1944 throws LDAPException 1945 { 1946 if (s == null) 1947 { 1948 return false; 1949 } 1950 1951 return equals(new RDN(s, schema)); 1952 } 1953 1954 1955 1956 /** 1957 * Indicates whether the two provided strings represent the same RDN. 1958 * 1959 * @param s1 The string representation of the first RDN for which to make 1960 * the determination. It must not be {@code null}. 1961 * @param s2 The string representation of the second RDN for which to make 1962 * the determination. It must not be {@code null}. 1963 * 1964 * @return {@code true} if the provided strings represent the same RDN, or 1965 * {@code false} if not. 1966 * 1967 * @throws LDAPException If either of the provided strings cannot be parsed 1968 * as an RDN. 1969 */ 1970 public static boolean equals(final String s1, final String s2) 1971 throws LDAPException 1972 { 1973 return new RDN(s1).equals(new RDN(s2)); 1974 } 1975 1976 1977 1978 /** 1979 * Compares the provided RDN to this RDN to determine their relative order in 1980 * a sorted list. 1981 * 1982 * @param rdn The RDN to compare against this RDN. It must not be 1983 * {@code null}. 1984 * 1985 * @return A negative integer if this RDN should come before the provided RDN 1986 * in a sorted list, a positive integer if this RDN should come after 1987 * the provided RDN in a sorted list, or zero if the provided RDN 1988 * can be considered equal to this RDN. 1989 */ 1990 @Override() 1991 public int compareTo(final RDN rdn) 1992 { 1993 return compare(this, rdn); 1994 } 1995 1996 1997 1998 /** 1999 * Compares the provided RDN values to determine their relative order in a 2000 * sorted list. 2001 * 2002 * @param rdn1 The first RDN to be compared. It must not be {@code null}. 2003 * @param rdn2 The second RDN to be compared. It must not be {@code null}. 2004 * 2005 * @return A negative integer if the first RDN should come before the second 2006 * RDN in a sorted list, a positive integer if the first RDN should 2007 * come after the second RDN in a sorted list, or zero if the two RDN 2008 * values can be considered equal. 2009 */ 2010 @Override() 2011 public int compare(final RDN rdn1, final RDN rdn2) 2012 { 2013 Validator.ensureNotNull(rdn1, rdn2); 2014 2015 final Iterator<RDNNameValuePair> iterator1 = 2016 rdn1.getNameValuePairs().iterator(); 2017 final Iterator<RDNNameValuePair> iterator2 = 2018 rdn2.getNameValuePairs().iterator(); 2019 2020 while (iterator1.hasNext()) 2021 { 2022 if (iterator2.hasNext()) 2023 { 2024 final RDNNameValuePair p1 = iterator1.next(); 2025 final RDNNameValuePair p2 = iterator2.next(); 2026 final int compareValue = p1.compareTo(p2); 2027 if (compareValue != 0) 2028 { 2029 return compareValue; 2030 } 2031 } 2032 else 2033 { 2034 return 1; 2035 } 2036 } 2037 2038 if (iterator2.hasNext()) 2039 { 2040 return -1; 2041 } 2042 else 2043 { 2044 return 0; 2045 } 2046 } 2047 2048 2049 2050 /** 2051 * Compares the RDN values with the provided string representations to 2052 * determine their relative order in a sorted list. 2053 * 2054 * @param s1 The string representation of the first RDN to be compared. It 2055 * must not be {@code null}. 2056 * @param s2 The string representation of the second RDN to be compared. It 2057 * must not be {@code null}. 2058 * 2059 * @return A negative integer if the first RDN should come before the second 2060 * RDN in a sorted list, a positive integer if the first RDN should 2061 * come after the second RDN in a sorted list, or zero if the two RDN 2062 * values can be considered equal. 2063 * 2064 * @throws LDAPException If either of the provided strings cannot be parsed 2065 * as an RDN. 2066 */ 2067 public static int compare(final String s1, final String s2) 2068 throws LDAPException 2069 { 2070 return compare(s1, s2, null); 2071 } 2072 2073 2074 2075 /** 2076 * Compares the RDN values with the provided string representations to 2077 * determine their relative order in a sorted list. 2078 * 2079 * @param s1 The string representation of the first RDN to be compared. 2080 * It must not be {@code null}. 2081 * @param s2 The string representation of the second RDN to be compared. 2082 * It must not be {@code null}. 2083 * @param schema The schema to use to generate the normalized string 2084 * representations of the RDNs. It may be {@code null} if no 2085 * schema is available. 2086 * 2087 * @return A negative integer if the first RDN should come before the second 2088 * RDN in a sorted list, a positive integer if the first RDN should 2089 * come after the second RDN in a sorted list, or zero if the two RDN 2090 * values can be considered equal. 2091 * 2092 * @throws LDAPException If either of the provided strings cannot be parsed 2093 * as an RDN. 2094 */ 2095 public static int compare(final String s1, final String s2, 2096 final Schema schema) 2097 throws LDAPException 2098 { 2099 return new RDN(s1, schema).compareTo(new RDN(s2, schema)); 2100 } 2101}