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 buffer.append(StaticUtils.toUTF8String(byteArray)); 1189 return pos; 1190 } 1191 1192 1193 1194 /** 1195 * Indicates whether the provided string represents a valid RDN. 1196 * 1197 * @param s The string for which to make the determination. It must not be 1198 * {@code null}. 1199 * 1200 * @return {@code true} if the provided string represents a valid RDN, or 1201 * {@code false} if not. 1202 */ 1203 public static boolean isValidRDN(final String s) 1204 { 1205 return isValidRDN(s, false); 1206 } 1207 1208 1209 1210 /** 1211 * Indicates whether the provided string represents a valid RDN. 1212 * 1213 * @param s The string for which to make the determination. 1214 * It must not be {@code null}. 1215 * @param strictNameChecking Indicates whether to verify that all attribute 1216 * type names are valid as per RFC 4514. If this 1217 * is {@code false}, then some technically invalid 1218 * characters may be accepted in attribute type 1219 * names. If this is {@code true}, then names 1220 * must be strictly compliant. 1221 * 1222 * @return {@code true} if the provided string represents a valid RDN, or 1223 * {@code false} if not. 1224 */ 1225 public static boolean isValidRDN(final String s, 1226 final boolean strictNameChecking) 1227 { 1228 try 1229 { 1230 new RDN(s, null, strictNameChecking); 1231 return true; 1232 } 1233 catch (final LDAPException le) 1234 { 1235 Debug.debugException(le); 1236 return false; 1237 } 1238 } 1239 1240 1241 1242 /** 1243 * Indicates whether this RDN contains multiple values. 1244 * 1245 * @return {@code true} if this RDN contains multiple values, or 1246 * {@code false} if not. 1247 */ 1248 public boolean isMultiValued() 1249 { 1250 return (attributeNames.length != 1); 1251 } 1252 1253 1254 1255 /** 1256 * Retrieves the number of values for this RDN. 1257 * 1258 * @return The number of values for this RDN. 1259 */ 1260 public int getValueCount() 1261 { 1262 return attributeNames.length; 1263 } 1264 1265 1266 1267 /** 1268 * Retrieves an array of the attributes that comprise this RDN. 1269 * 1270 * @return An array of the attributes that comprise this RDN. 1271 */ 1272 public Attribute[] getAttributes() 1273 { 1274 final Attribute[] attrs = new Attribute[attributeNames.length]; 1275 for (int i=0; i < attrs.length; i++) 1276 { 1277 attrs[i] = new Attribute(attributeNames[i], schema, 1278 new ASN1OctetString[] { attributeValues[i] }); 1279 } 1280 1281 return attrs; 1282 } 1283 1284 1285 1286 /** 1287 * Retrieves the set of attribute names for this RDN. 1288 * 1289 * @return The set of attribute names for this RDN. 1290 */ 1291 public String[] getAttributeNames() 1292 { 1293 return attributeNames; 1294 } 1295 1296 1297 1298 /** 1299 * Retrieves the set of attribute values for this RDN. 1300 * 1301 * @return The set of attribute values for this RDN. 1302 */ 1303 public String[] getAttributeValues() 1304 { 1305 final String[] stringValues = new String[attributeValues.length]; 1306 for (int i=0; i < stringValues.length; i++) 1307 { 1308 stringValues[i] = attributeValues[i].stringValue(); 1309 } 1310 1311 return stringValues; 1312 } 1313 1314 1315 1316 /** 1317 * Retrieves the set of attribute values for this RDN. 1318 * 1319 * @return The set of attribute values for this RDN. 1320 */ 1321 public byte[][] getByteArrayAttributeValues() 1322 { 1323 final byte[][] byteValues = new byte[attributeValues.length][]; 1324 for (int i=0; i < byteValues.length; i++) 1325 { 1326 byteValues[i] = attributeValues[i].getValue(); 1327 } 1328 1329 return byteValues; 1330 } 1331 1332 1333 1334 /** 1335 * Retrieves a sorted set of the name-value pairs that comprise this RDN. 1336 * 1337 * @return A sorted set of the name-value pairs that comprise this RDN. 1338 */ 1339 public SortedSet<RDNNameValuePair> getNameValuePairs() 1340 { 1341 if (nameValuePairs == null) 1342 { 1343 final SortedSet<RDNNameValuePair> s = new TreeSet<>(); 1344 for (int i=0; i < attributeNames.length; i++) 1345 { 1346 s.add(new RDNNameValuePair(attributeNames[i], attributeValues[i], 1347 schema)); 1348 } 1349 1350 nameValuePairs = Collections.unmodifiableSortedSet(s); 1351 } 1352 1353 return nameValuePairs; 1354 } 1355 1356 1357 1358 /** 1359 * Retrieves the schema that will be used for this RDN, if any. 1360 * 1361 * @return The schema that will be used for this RDN, or {@code null} if none 1362 * has been provided. 1363 */ 1364 Schema getSchema() 1365 { 1366 return schema; 1367 } 1368 1369 1370 1371 /** 1372 * Indicates whether this RDN contains the specified attribute. 1373 * 1374 * @param attributeName The name of the attribute for which to make the 1375 * determination. 1376 * 1377 * @return {@code true} if RDN contains the specified attribute, or 1378 * {@code false} if not. 1379 */ 1380 public boolean hasAttribute(final String attributeName) 1381 { 1382 for (final RDNNameValuePair nameValuePair : getNameValuePairs()) 1383 { 1384 if (nameValuePair.hasAttributeName(attributeName)) 1385 { 1386 return true; 1387 } 1388 } 1389 1390 return false; 1391 } 1392 1393 1394 1395 /** 1396 * Indicates whether this RDN contains the specified attribute value. 1397 * 1398 * @param attributeName The name of the attribute for which to make the 1399 * determination. 1400 * @param attributeValue The attribute value for which to make the 1401 * determination. 1402 * 1403 * @return {@code true} if RDN contains the specified attribute, or 1404 * {@code false} if not. 1405 */ 1406 public boolean hasAttributeValue(final String attributeName, 1407 final String attributeValue) 1408 { 1409 for (final RDNNameValuePair nameValuePair : getNameValuePairs()) 1410 { 1411 if (nameValuePair.hasAttributeName(attributeName) && 1412 nameValuePair.hasAttributeValue(attributeValue)) 1413 { 1414 return true; 1415 } 1416 } 1417 1418 return false; 1419 } 1420 1421 1422 1423 /** 1424 * Indicates whether this RDN contains the specified attribute value. 1425 * 1426 * @param attributeName The name of the attribute for which to make the 1427 * determination. 1428 * @param attributeValue The attribute value for which to make the 1429 * determination. 1430 * 1431 * @return {@code true} if RDN contains the specified attribute, or 1432 * {@code false} if not. 1433 */ 1434 public boolean hasAttributeValue(final String attributeName, 1435 final byte[] attributeValue) 1436 { 1437 for (final RDNNameValuePair nameValuePair : getNameValuePairs()) 1438 { 1439 if (nameValuePair.hasAttributeName(attributeName) && 1440 nameValuePair.hasAttributeValue(attributeValue)) 1441 { 1442 return true; 1443 } 1444 } 1445 1446 return false; 1447 } 1448 1449 1450 1451 /** 1452 * Retrieves a string representation of this RDN. 1453 * 1454 * @return A string representation of this RDN. 1455 */ 1456 @Override() 1457 public String toString() 1458 { 1459 if (rdnString == null) 1460 { 1461 final StringBuilder buffer = new StringBuilder(); 1462 toString(buffer, false); 1463 rdnString = buffer.toString(); 1464 } 1465 1466 return rdnString; 1467 } 1468 1469 1470 1471 /** 1472 * Retrieves a string representation of this RDN with minimal encoding for 1473 * special characters. Only those characters specified in RFC 4514 section 1474 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or 1475 * non-printable ASCII characters. 1476 * 1477 * @return A string representation of this RDN with minimal encoding for 1478 * special characters. 1479 */ 1480 public String toMinimallyEncodedString() 1481 { 1482 final StringBuilder buffer = new StringBuilder(); 1483 toString(buffer, true); 1484 return buffer.toString(); 1485 } 1486 1487 1488 1489 /** 1490 * Appends a string representation of this RDN to the provided buffer. 1491 * 1492 * @param buffer The buffer to which the string representation is to be 1493 * appended. 1494 */ 1495 public void toString(final StringBuilder buffer) 1496 { 1497 toString(buffer, false); 1498 } 1499 1500 1501 1502 /** 1503 * Appends a string representation of this RDN to the provided buffer. 1504 * 1505 * @param buffer The buffer to which the string representation is 1506 * to be appended. 1507 * @param minimizeEncoding Indicates whether to restrict the encoding of 1508 * special characters to the bare minimum required 1509 * by LDAP (as per RFC 4514 section 2.4). If this 1510 * is {@code true}, then only leading and trailing 1511 * spaces, double quotes, plus signs, commas, 1512 * semicolons, greater-than, less-than, and 1513 * backslash characters will be encoded. 1514 */ 1515 public void toString(final StringBuilder buffer, 1516 final boolean minimizeEncoding) 1517 { 1518 if ((rdnString != null) && (! minimizeEncoding)) 1519 { 1520 buffer.append(rdnString); 1521 return; 1522 } 1523 1524 for (int i=0; i < attributeNames.length; i++) 1525 { 1526 if (i > 0) 1527 { 1528 buffer.append('+'); 1529 } 1530 1531 buffer.append(attributeNames[i]); 1532 buffer.append('='); 1533 appendValue(buffer, attributeValues[i], minimizeEncoding); 1534 } 1535 } 1536 1537 1538 1539 /** 1540 * Appends an appropriately escaped version of the provided value to the given 1541 * buffer. 1542 * 1543 * @param buffer The buffer to which the value should be appended. 1544 * It must not be {@code null}. 1545 * @param value The value to be appended in an appropriately 1546 * escaped form. It must not be {@code null}. 1547 * @param minimizeEncoding Indicates whether to restrict the encoding of 1548 * special characters to the bare minimum required 1549 * by LDAP (as per RFC 4514 section 2.4). If this 1550 * is {@code true}, then only leading and trailing 1551 * spaces, double quotes, plus signs, commas, 1552 * semicolons, greater-than, less-than, and 1553 * backslash characters will be encoded. 1554 */ 1555 static void appendValue(final StringBuilder buffer, 1556 final ASN1OctetString value, 1557 final boolean minimizeEncoding) 1558 { 1559 final String valueString = value.stringValue(); 1560 final int length = valueString.length(); 1561 for (int j=0; j < length; j++) 1562 { 1563 final char c = valueString.charAt(j); 1564 switch (c) 1565 { 1566 case '\\': 1567 case '=': 1568 case '"': 1569 case '+': 1570 case ',': 1571 case ';': 1572 case '<': 1573 case '>': 1574 // These characters will always be escaped. 1575 buffer.append('\\'); 1576 buffer.append(c); 1577 break; 1578 1579 case '#': 1580 // Escape the octothorpe only if it's the first character. 1581 if (j == 0) 1582 { 1583 buffer.append("\\#"); 1584 } 1585 else 1586 { 1587 buffer.append('#'); 1588 } 1589 break; 1590 1591 case ' ': 1592 // Escape this space only if it's the first or last character. 1593 if ((j == 0) || ((j+1) == length)) 1594 { 1595 buffer.append("\\ "); 1596 } 1597 else 1598 { 1599 buffer.append(' '); 1600 } 1601 break; 1602 1603 case '\u0000': 1604 buffer.append("\\00"); 1605 break; 1606 1607 default: 1608 // If it's not a printable ASCII character, then hex-encode it 1609 // unless we're using minimized encoding. 1610 if ((! minimizeEncoding) && ((c < ' ') || (c > '~'))) 1611 { 1612 StaticUtils.hexEncode(c, buffer); 1613 } 1614 else 1615 { 1616 buffer.append(c); 1617 } 1618 break; 1619 } 1620 } 1621 } 1622 1623 1624 1625 /** 1626 * Retrieves a normalized string representation of this RDN. 1627 * 1628 * @return A normalized string representation of this RDN. 1629 */ 1630 public String toNormalizedString() 1631 { 1632 if (normalizedString == null) 1633 { 1634 final StringBuilder buffer = new StringBuilder(); 1635 toNormalizedString(buffer); 1636 normalizedString = buffer.toString(); 1637 } 1638 1639 return normalizedString; 1640 } 1641 1642 1643 1644 /** 1645 * Appends a normalized string representation of this RDN to the provided 1646 * buffer. 1647 * 1648 * @param buffer The buffer to which the normalized string representation is 1649 * to be appended. 1650 */ 1651 public void toNormalizedString(final StringBuilder buffer) 1652 { 1653 if (attributeNames.length == 1) 1654 { 1655 // It's a single-valued RDN, so there is no need to sort anything. 1656 final String name = normalizeAttrName(attributeNames[0]); 1657 buffer.append(name); 1658 buffer.append('='); 1659 appendNormalizedValue(buffer, name, attributeValues[0], schema); 1660 } 1661 else 1662 { 1663 // It's a multivalued RDN, so we need to sort the components. 1664 final Iterator<RDNNameValuePair> iterator = 1665 getNameValuePairs().iterator(); 1666 while (iterator.hasNext()) 1667 { 1668 buffer.append(iterator.next().toNormalizedString()); 1669 if (iterator.hasNext()) 1670 { 1671 buffer.append('+'); 1672 } 1673 } 1674 } 1675 } 1676 1677 1678 1679 /** 1680 * Obtains a normalized representation of the provided attribute name. 1681 * 1682 * @param name The name of the attribute for which to create the normalized 1683 * representation. 1684 * 1685 * @return A normalized representation of the provided attribute name. 1686 */ 1687 private String normalizeAttrName(final String name) 1688 { 1689 String n = name; 1690 if (schema != null) 1691 { 1692 final AttributeTypeDefinition at = schema.getAttributeType(name); 1693 if (at != null) 1694 { 1695 n = at.getNameOrOID(); 1696 } 1697 } 1698 return StaticUtils.toLowerCase(n); 1699 } 1700 1701 1702 1703 /** 1704 * Retrieves a normalized string representation of the RDN with the provided 1705 * string representation. 1706 * 1707 * @param s The string representation of the RDN to normalize. It must not 1708 * be {@code null}. 1709 * 1710 * @return The normalized string representation of the RDN with the provided 1711 * string representation. 1712 * 1713 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1714 */ 1715 public static String normalize(final String s) 1716 throws LDAPException 1717 { 1718 return normalize(s, null); 1719 } 1720 1721 1722 1723 /** 1724 * Retrieves a normalized string representation of the RDN with the provided 1725 * string representation. 1726 * 1727 * @param s The string representation of the RDN to normalize. It must 1728 * not be {@code null}. 1729 * @param schema The schema to use to generate the normalized string 1730 * representation of the RDN. It may be {@code null} if no 1731 * schema is available. 1732 * 1733 * @return The normalized string representation of the RDN with the provided 1734 * string representation. 1735 * 1736 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1737 */ 1738 public static String normalize(final String s, final Schema schema) 1739 throws LDAPException 1740 { 1741 return new RDN(s, schema).toNormalizedString(); 1742 } 1743 1744 1745 1746 /** 1747 * Appends a normalized string representation of the provided attribute value 1748 * to the given buffer. 1749 * 1750 * @param buffer The buffer to which the value should be appended. 1751 * It must not be {@code null}. 1752 * @param attributeName The name of the attribute whose value is to be 1753 * normalized. It must not be {@code null}. 1754 * @param value The value to be normalized. It must not be 1755 * {@code null}. 1756 * @param schema The schema to use to generate the normalized 1757 * representation of the value. It may be {@code null} 1758 * if no schema is available. 1759 */ 1760 static void appendNormalizedValue(final StringBuilder buffer, 1761 final String attributeName, 1762 final ASN1OctetString value, 1763 final Schema schema) 1764 { 1765 final MatchingRule matchingRule = 1766 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 1767 1768 ASN1OctetString rawNormValue; 1769 try 1770 { 1771 rawNormValue = matchingRule.normalize(value); 1772 } 1773 catch (final Exception e) 1774 { 1775 Debug.debugException(e); 1776 rawNormValue = 1777 new ASN1OctetString(StaticUtils.toLowerCase(value.stringValue())); 1778 } 1779 1780 final String valueString = rawNormValue.stringValue(); 1781 final int length = valueString.length(); 1782 for (int i=0; i < length; i++) 1783 { 1784 final char c = valueString.charAt(i); 1785 1786 switch (c) 1787 { 1788 case '\\': 1789 case '=': 1790 case '"': 1791 case '+': 1792 case ',': 1793 case ';': 1794 case '<': 1795 case '>': 1796 buffer.append('\\'); 1797 buffer.append(c); 1798 break; 1799 1800 case '#': 1801 // Escape the octothorpe only if it's the first character. 1802 if (i == 0) 1803 { 1804 buffer.append("\\#"); 1805 } 1806 else 1807 { 1808 buffer.append('#'); 1809 } 1810 break; 1811 1812 case ' ': 1813 // Escape this space only if it's the first or last character. 1814 if ((i == 0) || ((i+1) == length)) 1815 { 1816 buffer.append("\\ "); 1817 } 1818 else 1819 { 1820 buffer.append(' '); 1821 } 1822 break; 1823 1824 default: 1825 // If it's a printable ASCII character that isn't covered by one of 1826 // the above options, then just append it to the buffer. Otherwise, 1827 // hex-encode all bytes that comprise its UTF-8 representation, which 1828 // might require special handling if it requires two Java characters 1829 // to encode the Unicode character. 1830 if ((c >= ' ') && (c <= '~')) 1831 { 1832 buffer.append(c); 1833 } 1834 else if (Character.isHighSurrogate(c)) 1835 { 1836 if (((i+1) < length) && 1837 Character.isLowSurrogate(valueString.charAt(i+1))) 1838 { 1839 final char c2 = valueString.charAt(++i); 1840 final int codePoint = Character.toCodePoint(c, c2); 1841 StaticUtils.hexEncode(codePoint, buffer); 1842 } 1843 else 1844 { 1845 // This should never happen. 1846 StaticUtils.hexEncode(c, buffer); 1847 } 1848 } 1849 else 1850 { 1851 StaticUtils.hexEncode(c, buffer); 1852 } 1853 break; 1854 } 1855 } 1856 } 1857 1858 1859 1860 /** 1861 * Retrieves a hash code for this RDN. 1862 * 1863 * @return The hash code for this RDN. 1864 */ 1865 @Override() 1866 public int hashCode() 1867 { 1868 return toNormalizedString().hashCode(); 1869 } 1870 1871 1872 1873 /** 1874 * Indicates whether this RDN is equal to the provided object. The given 1875 * object will only be considered equal to this RDN if it is also an RDN with 1876 * the same set of names and values. 1877 * 1878 * @param o The object for which to make the determination. 1879 * 1880 * @return {@code true} if the provided object can be considered equal to 1881 * this RDN, or {@code false} if not. 1882 */ 1883 @Override() 1884 public boolean equals(final Object o) 1885 { 1886 if (o == null) 1887 { 1888 return false; 1889 } 1890 1891 if (o == this) 1892 { 1893 return true; 1894 } 1895 1896 if (! (o instanceof RDN)) 1897 { 1898 return false; 1899 } 1900 1901 final RDN rdn = (RDN) o; 1902 return (toNormalizedString().equals(rdn.toNormalizedString())); 1903 } 1904 1905 1906 1907 /** 1908 * Indicates whether the RDN with the provided string representation is equal 1909 * to this RDN. 1910 * 1911 * @param s The string representation of the DN to compare with this RDN. 1912 * 1913 * @return {@code true} if the DN with the provided string representation is 1914 * equal to this RDN, or {@code false} if not. 1915 * 1916 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1917 */ 1918 public boolean equals(final String s) 1919 throws LDAPException 1920 { 1921 if (s == null) 1922 { 1923 return false; 1924 } 1925 1926 return equals(new RDN(s, schema)); 1927 } 1928 1929 1930 1931 /** 1932 * Indicates whether the two provided strings represent the same RDN. 1933 * 1934 * @param s1 The string representation of the first RDN for which to make 1935 * the determination. It must not be {@code null}. 1936 * @param s2 The string representation of the second RDN for which to make 1937 * the determination. It must not be {@code null}. 1938 * 1939 * @return {@code true} if the provided strings represent the same RDN, or 1940 * {@code false} if not. 1941 * 1942 * @throws LDAPException If either of the provided strings cannot be parsed 1943 * as an RDN. 1944 */ 1945 public static boolean equals(final String s1, final String s2) 1946 throws LDAPException 1947 { 1948 return new RDN(s1).equals(new RDN(s2)); 1949 } 1950 1951 1952 1953 /** 1954 * Compares the provided RDN to this RDN to determine their relative order in 1955 * a sorted list. 1956 * 1957 * @param rdn The RDN to compare against this RDN. It must not be 1958 * {@code null}. 1959 * 1960 * @return A negative integer if this RDN should come before the provided RDN 1961 * in a sorted list, a positive integer if this RDN should come after 1962 * the provided RDN in a sorted list, or zero if the provided RDN 1963 * can be considered equal to this RDN. 1964 */ 1965 @Override() 1966 public int compareTo(final RDN rdn) 1967 { 1968 return compare(this, rdn); 1969 } 1970 1971 1972 1973 /** 1974 * Compares the provided RDN values to determine their relative order in a 1975 * sorted list. 1976 * 1977 * @param rdn1 The first RDN to be compared. It must not be {@code null}. 1978 * @param rdn2 The second RDN to be compared. It must not be {@code null}. 1979 * 1980 * @return A negative integer if the first RDN should come before the second 1981 * RDN in a sorted list, a positive integer if the first RDN should 1982 * come after the second RDN in a sorted list, or zero if the two RDN 1983 * values can be considered equal. 1984 */ 1985 @Override() 1986 public int compare(final RDN rdn1, final RDN rdn2) 1987 { 1988 Validator.ensureNotNull(rdn1, rdn2); 1989 1990 final Iterator<RDNNameValuePair> iterator1 = 1991 rdn1.getNameValuePairs().iterator(); 1992 final Iterator<RDNNameValuePair> iterator2 = 1993 rdn2.getNameValuePairs().iterator(); 1994 1995 while (iterator1.hasNext()) 1996 { 1997 if (iterator2.hasNext()) 1998 { 1999 final RDNNameValuePair p1 = iterator1.next(); 2000 final RDNNameValuePair p2 = iterator2.next(); 2001 final int compareValue = p1.compareTo(p2); 2002 if (compareValue != 0) 2003 { 2004 return compareValue; 2005 } 2006 } 2007 else 2008 { 2009 return 1; 2010 } 2011 } 2012 2013 if (iterator2.hasNext()) 2014 { 2015 return -1; 2016 } 2017 else 2018 { 2019 return 0; 2020 } 2021 } 2022 2023 2024 2025 /** 2026 * Compares the RDN values with the provided string representations to 2027 * determine their relative order in a sorted list. 2028 * 2029 * @param s1 The string representation of the first RDN to be compared. It 2030 * must not be {@code null}. 2031 * @param s2 The string representation of the second RDN to be compared. It 2032 * must not be {@code null}. 2033 * 2034 * @return A negative integer if the first RDN should come before the second 2035 * RDN in a sorted list, a positive integer if the first RDN should 2036 * come after the second RDN in a sorted list, or zero if the two RDN 2037 * values can be considered equal. 2038 * 2039 * @throws LDAPException If either of the provided strings cannot be parsed 2040 * as an RDN. 2041 */ 2042 public static int compare(final String s1, final String s2) 2043 throws LDAPException 2044 { 2045 return compare(s1, s2, null); 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. 2055 * It must not be {@code null}. 2056 * @param s2 The string representation of the second RDN to be compared. 2057 * It must not be {@code null}. 2058 * @param schema The schema to use to generate the normalized string 2059 * representations of the RDNs. It may be {@code null} if no 2060 * schema is available. 2061 * 2062 * @return A negative integer if the first RDN should come before the second 2063 * RDN in a sorted list, a positive integer if the first RDN should 2064 * come after the second RDN in a sorted list, or zero if the two RDN 2065 * values can be considered equal. 2066 * 2067 * @throws LDAPException If either of the provided strings cannot be parsed 2068 * as an RDN. 2069 */ 2070 public static int compare(final String s1, final String s2, 2071 final Schema schema) 2072 throws LDAPException 2073 { 2074 return new RDN(s1, schema).compareTo(new RDN(s2, schema)); 2075 } 2076}