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