1 /* 2 * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.jndi.ldap; 27 28 29 import java.util.Enumeration; 30 import java.util.Vector; 31 32 import javax.naming.*; 33 import javax.naming.directory.Attributes; 34 import javax.naming.directory.Attribute; 35 import javax.naming.directory.BasicAttributes; 36 37 38 /** 39 * <code>LdapName</code> implements compound names for LDAP v3 as 40 * specified by RFC 2253. 41 *<p> 42 * RFC 2253 has a few ambiguities and outright inconsistencies. These 43 * are resolved as follows: 44 * <ul> 45 * <li> RFC 2253 leaves the term "whitespace" undefined. The 46 * definition of "optional-space" given in RFC 1779 is used in 47 * its place: either a space character or a carriage return ("\r"). 48 * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'. 49 * Such whitespace is accepted but not generated by this code, 50 * and is ignored when comparing names. 51 * <li> AttributeValue strings containing '=' or non-leading '#' 52 * characters (unescaped) are accepted. 53 * </ul> 54 *<p> 55 * String names passed to <code>LdapName</code> or returned by it 56 * use the full 16-bit Unicode character set. They may also contain 57 * characters encoded into UTF-8 with each octet represented by a 58 * three-character substring such as "\\B4". 59 * They may not, however, contain characters encoded into UTF-8 with 60 * each octet represented by a single character in the string: the 61 * meaning would be ambiguous. 62 *<p> 63 * <code>LdapName</code> will properly parse all valid names, but 64 * does not attempt to detect all possible violations when parsing 65 * invalid names. It's "generous". 66 *<p> 67 * When names are tested for equality, attribute types and binary 68 * values are case-insensitive, and string values are by default 69 * case-insensitive. 70 * String values with different but equivalent usage of quoting, 71 * escaping, or UTF8-hex-encoding are considered equal. The order of 72 * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not 73 * significant. 74 * 75 * @author Scott Seligman 76 */ 77 78 public final class LdapName implements Name { 79 80 private transient String unparsed; // if non-null, the DN in unparsed form 81 private transient Vector rdns; // parsed name components 82 private transient boolean valuesCaseSensitive = false; 83 84 /** 85 * Constructs an LDAP name from the given DN. 86 * 87 * @param name An LDAP DN. To JNDI, a compound name. 88 * 89 * @throws InvalidNameException if a syntax violation is detected. 90 */ 91 public LdapName(String name) throws InvalidNameException { 92 unparsed = name; 93 parse(); 94 } 95 96 /* 97 * Constructs an LDAP name given its parsed components and, optionally 98 * (if "name" is not null), the unparsed DN. 99 */ 100 private LdapName(String name, Vector rdns) { 101 unparsed = name; 102 this.rdns = (Vector)rdns.clone(); 103 } 104 105 /* 106 * Constructs an LDAP name given its parsed components (the elements 107 * of "rdns" in the range [beg,end)) and, optionally 108 * (if "name" is not null), the unparsed DN. 109 */ 110 private LdapName(String name, Vector rdns, int beg, int end) { 111 unparsed = name; 112 this.rdns = new Vector(); 113 for (int i = beg; i < end; i++) { 114 this.rdns.addElement(rdns.elementAt(i)); 115 } 116 } 117 118 119 public Object clone() { 120 return new LdapName(unparsed, rdns); 121 } 122 123 public String toString() { 124 if (unparsed != null) { 125 return unparsed; 126 } 127 128 StringBuffer buf = new StringBuffer(); 129 for (int i = rdns.size() - 1; i >= 0; i--) { 130 if (i < rdns.size() - 1) { 131 buf.append(','); 132 } 133 Rdn rdn = (Rdn)rdns.elementAt(i); 134 buf.append(rdn); 135 } 136 137 unparsed = new String(buf); 138 return unparsed; 139 } 140 141 public boolean equals(Object obj) { 142 return ((obj instanceof LdapName) && 143 (compareTo(obj) == 0)); 144 } 145 146 public int compareTo(Object obj) { 147 LdapName that = (LdapName)obj; 148 149 if ((obj == this) || // check possible shortcuts 150 (unparsed != null && unparsed.equals(that.unparsed))) { 151 return 0; 152 } 153 154 // Compare RDNs one by one, lexicographically. 155 int minSize = Math.min(rdns.size(), that.rdns.size()); 156 for (int i = 0 ; i < minSize; i++) { 157 // Compare a single pair of RDNs. 158 Rdn rdn1 = (Rdn)rdns.elementAt(i); 159 Rdn rdn2 = (Rdn)that.rdns.elementAt(i); 160 161 int diff = rdn1.compareTo(rdn2); 162 if (diff != 0) { 163 return diff; 164 } 165 } 166 return (rdns.size() - that.rdns.size()); // longer DN wins 167 } 168 169 public int hashCode() { 170 // Sum up the hash codes of the components. 171 int hash = 0; 172 173 // For each RDN... 174 for (int i = 0; i < rdns.size(); i++) { 175 Rdn rdn = (Rdn)rdns.elementAt(i); 176 hash += rdn.hashCode(); 177 } 178 return hash; 179 } 180 181 public int size() { 182 return rdns.size(); 183 } 184 185 public boolean isEmpty() { 186 return rdns.isEmpty(); 187 } 188 189 public Enumeration getAll() { 190 final Enumeration enum_ = rdns.elements(); 191 192 return new Enumeration () { 193 public boolean hasMoreElements() { 194 return enum_.hasMoreElements(); 195 } 196 public Object nextElement() { 197 return enum_.nextElement().toString(); 198 } 199 }; 200 } 201 202 public String get(int pos) { 203 return rdns.elementAt(pos).toString(); 204 } 205 206 public Name getPrefix(int pos) { 207 return new LdapName(null, rdns, 0, pos); 208 } 209 210 public Name getSuffix(int pos) { 211 return new LdapName(null, rdns, pos, rdns.size()); 212 } 213 214 public boolean startsWith(Name n) { 215 int len1 = rdns.size(); 216 int len2 = n.size(); 217 return (len1 >= len2 && 218 matches(0, len2, n)); 219 } 220 221 public boolean endsWith(Name n) { 222 int len1 = rdns.size(); 223 int len2 = n.size(); 224 return (len1 >= len2 && 225 matches(len1 - len2, len1, n)); 226 } 227 228 /** 229 * Controls whether string-values are treated as case-sensitive 230 * when the string values within names are compared. The default 231 * behavior is case-insensitive comparison. 232 */ 233 public void setValuesCaseSensitive(boolean caseSensitive) { 234 toString(); 235 rdns = null; // clear any cached information 236 try { 237 parse(); 238 } catch (InvalidNameException e) { 239 // shouldn't happen 240 throw new IllegalStateException("Cannot parse name: " + unparsed); 241 } 242 valuesCaseSensitive = caseSensitive; 243 } 244 245 /* 246 * Helper method for startsWith() and endsWith(). 247 * Returns true if components [beg,end) match the components of "n". 248 * If "n" is not an LdapName, each of its components is parsed as 249 * the string form of an RDN. 250 * The following must hold: end - beg == n.size(). 251 */ 252 private boolean matches(int beg, int end, Name n) { 253 for (int i = beg; i < end; i++) { 254 Rdn rdn; 255 if (n instanceof LdapName) { 256 LdapName ln = (LdapName)n; 257 rdn = (Rdn)ln.rdns.elementAt(i - beg); 258 } else { 259 String rdnString = n.get(i - beg); 260 try { 261 rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); 262 } catch (InvalidNameException e) { 263 return false; 264 } 265 } 266 267 if (!rdn.equals(rdns.elementAt(i))) { 268 return false; 269 } 270 } 271 return true; 272 } 273 274 public Name addAll(Name suffix) throws InvalidNameException { 275 return addAll(size(), suffix); 276 } 277 278 /* 279 * If "suffix" is not an LdapName, each of its components is parsed as 280 * the string form of an RDN. 281 */ 282 public Name addAll(int pos, Name suffix) throws InvalidNameException { 283 if (suffix instanceof LdapName) { 284 LdapName s = (LdapName)suffix; 285 for (int i = 0; i < s.rdns.size(); i++) { 286 rdns.insertElementAt(s.rdns.elementAt(i), pos++); 287 } 288 } else { 289 Enumeration comps = suffix.getAll(); 290 while (comps.hasMoreElements()) { 291 DnParser p = new DnParser((String)comps.nextElement(), 292 valuesCaseSensitive); 293 rdns.insertElementAt(p.getRdn(), pos++); 294 } 295 } 296 unparsed = null; // no longer valid 297 return this; 298 } 299 300 public Name add(String comp) throws InvalidNameException { 301 return add(size(), comp); 302 } 303 304 public Name add(int pos, String comp) throws InvalidNameException { 305 Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); 306 rdns.insertElementAt(rdn, pos); 307 unparsed = null; // no longer valid 308 return this; 309 } 310 311 public Object remove(int pos) throws InvalidNameException { 312 String comp = get(pos); 313 rdns.removeElementAt(pos); 314 unparsed = null; // no longer valid 315 return comp; 316 } 317 318 319 private void parse() throws InvalidNameException { 320 rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn(); 321 } 322 323 /* 324 * Best guess as to what RFC 2253 means by "whitespace". 325 */ 326 private static boolean isWhitespace(char c) { 327 return (c == ' ' || c == '\r'); 328 } 329 330 /** 331 * Given the value of an attribute, returns a string suitable 332 * for inclusion in a DN. If the value is a string, this is 333 * accomplished by using backslash (\) to escape the following 334 * characters: 335 *<ul> 336 *<li>leading and trailing whitespace 337 *<li><pre>, = + < > # ; " \</pre> 338 *</ul> 339 * If the value is a byte array, it is converted to hex 340 * notation (such as "#CEB1DF80"). 341 */ 342 public static String escapeAttributeValue(Object val) { 343 return TypeAndValue.escapeValue(val); 344 } 345 346 /** 347 * Given an attribute value formated according to RFC 2253, 348 * returns the unformated value. Returns a string value as 349 * a string, and a binary value as a byte array. 350 */ 351 public static Object unescapeAttributeValue(String val) { 352 return TypeAndValue.unescapeValue(val); 353 } 354 355 /** 356 * Serializes only the unparsed DN, for compactness and to avoid 357 * any implementation dependency. 358 * 359 * @serialdata The DN string and a boolean indicating whether 360 * the values are case sensitive. 361 */ 362 private void writeObject(java.io.ObjectOutputStream s) 363 throws java.io.IOException { 364 s.writeObject(toString()); 365 s.writeBoolean(valuesCaseSensitive); 366 } 367 368 private void readObject(java.io.ObjectInputStream s) 369 throws java.io.IOException, ClassNotFoundException { 370 unparsed = (String)s.readObject(); 371 valuesCaseSensitive = s.readBoolean(); 372 try { 373 parse(); 374 } catch (InvalidNameException e) { 375 // shouldn't happen 376 throw new java.io.StreamCorruptedException( 377 "Invalid name: " + unparsed); 378 } 379 } 380 381 static final long serialVersionUID = -1595520034788997356L; 382 383 384 /* 385 * DnParser implements a recursive descent parser for a single DN. 386 */ 387 static class DnParser { 388 389 private final String name; // DN being parsed 390 private final char[] chars; // characters in LDAP name being parsed 391 private final int len; // length of "chars" 392 private int cur = 0; // index of first unconsumed char in "chars" 393 private boolean valuesCaseSensitive; 394 395 /* 396 * Given an LDAP DN in string form, returns a parser for it. 397 */ 398 DnParser(String name, boolean valuesCaseSensitive) 399 throws InvalidNameException { 400 this.name = name; 401 len = name.length(); 402 chars = name.toCharArray(); 403 this.valuesCaseSensitive = valuesCaseSensitive; 404 } 405 406 /* 407 * Parses the DN, returning a Vector of its RDNs. 408 */ 409 Vector getDn() throws InvalidNameException { 410 cur = 0; 411 Vector rdns = new Vector(len / 3 + 10); // leave room for growth 412 413 if (len == 0) { 414 return rdns; 415 } 416 417 rdns.addElement(parseRdn()); 418 while (cur < len) { 419 if (chars[cur] == ',' || chars[cur] == ';') { 420 ++cur; 421 rdns.insertElementAt(parseRdn(), 0); 422 } else { 423 throw new InvalidNameException("Invalid name: " + name); 424 } 425 } 426 return rdns; 427 } 428 429 /* 430 * Parses the DN, if it is known to contain a single RDN. 431 */ 432 Rdn getRdn() throws InvalidNameException { 433 Rdn rdn = parseRdn(); 434 if (cur < len) { 435 throw new InvalidNameException("Invalid RDN: " + name); 436 } 437 return rdn; 438 } 439 440 /* 441 * Parses the next RDN and returns it. Throws an exception if 442 * none is found. Leading and trailing whitespace is consumed. 443 */ 444 private Rdn parseRdn() throws InvalidNameException { 445 446 Rdn rdn = new Rdn(); 447 while (cur < len) { 448 consumeWhitespace(); 449 String attrType = parseAttrType(); 450 consumeWhitespace(); 451 if (cur >= len || chars[cur] != '=') { 452 throw new InvalidNameException("Invalid name: " + name); 453 } 454 ++cur; // consume '=' 455 consumeWhitespace(); 456 String value = parseAttrValue(); 457 consumeWhitespace(); 458 459 rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive)); 460 if (cur >= len || chars[cur] != '+') { 461 break; 462 } 463 ++cur; // consume '+' 464 } 465 return rdn; 466 } 467 468 /* 469 * Returns the attribute type that begins at the next unconsumed 470 * char. No leading whitespace is expected. 471 * This routine is more generous than RFC 2253. It accepts 472 * attribute types composed of any nonempty combination of Unicode 473 * letters, Unicode digits, '.', '-', and internal space characters. 474 */ 475 private String parseAttrType() throws InvalidNameException { 476 477 final int beg = cur; 478 while (cur < len) { 479 char c = chars[cur]; 480 if (Character.isLetterOrDigit(c) || 481 c == '.' || 482 c == '-' || 483 c == ' ') { 484 ++cur; 485 } else { 486 break; 487 } 488 } 489 // Back out any trailing spaces. 490 while ((cur > beg) && (chars[cur - 1] == ' ')) { 491 --cur; 492 } 493 494 if (beg == cur) { 495 throw new InvalidNameException("Invalid name: " + name); 496 } 497 return new String(chars, beg, cur - beg); 498 } 499 500 /* 501 * Returns the attribute value that begins at the next unconsumed 502 * char. No leading whitespace is expected. 503 */ 504 private String parseAttrValue() throws InvalidNameException { 505 506 if (cur < len && chars[cur] == '#') { 507 return parseBinaryAttrValue(); 508 } else if (cur < len && chars[cur] == '"') { 509 return parseQuotedAttrValue(); 510 } else { 511 return parseStringAttrValue(); 512 } 513 } 514 515 private String parseBinaryAttrValue() throws InvalidNameException { 516 final int beg = cur; 517 ++cur; // consume '#' 518 while (cur < len && 519 Character.isLetterOrDigit(chars[cur])) { 520 ++cur; 521 } 522 return new String(chars, beg, cur - beg); 523 } 524 525 private String parseQuotedAttrValue() throws InvalidNameException { 526 527 final int beg = cur; 528 ++cur; // consume '"' 529 530 while ((cur < len) && chars[cur] != '"') { 531 if (chars[cur] == '\\') { 532 ++cur; // consume backslash, then what follows 533 } 534 ++cur; 535 } 536 if (cur >= len) { // no closing quote 537 throw new InvalidNameException("Invalid name: " + name); 538 } 539 ++cur ; // consume closing quote 540 541 return new String(chars, beg, cur - beg); 542 } 543 544 private String parseStringAttrValue() throws InvalidNameException { 545 546 final int beg = cur; 547 int esc = -1; // index of the most recently escaped character 548 549 while ((cur < len) && !atTerminator()) { 550 if (chars[cur] == '\\') { 551 ++cur; // consume backslash, then what follows 552 esc = cur; 553 } 554 ++cur; 555 } 556 if (cur > len) { // 'twas backslash followed by nothing 557 throw new InvalidNameException("Invalid name: " + name); 558 } 559 560 // Trim off (unescaped) trailing whitespace. 561 int end; 562 for (end = cur; end > beg; end--) { 563 if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) { 564 break; 565 } 566 } 567 return new String(chars, beg, end - beg); 568 } 569 570 private void consumeWhitespace() { 571 while ((cur < len) && isWhitespace(chars[cur])) { 572 ++cur; 573 } 574 } 575 576 /* 577 * Returns true if next unconsumed character is one that terminates 578 * a string attribute value. 579 */ 580 private boolean atTerminator() { 581 return (cur < len && 582 (chars[cur] == ',' || 583 chars[cur] == ';' || 584 chars[cur] == '+')); 585 } 586 } 587 588 589 /* 590 * Class Rdn represents a set of TypeAndValue. 591 */ 592 static class Rdn { 593 594 /* 595 * A vector of the TypeAndValue elements of this Rdn. 596 * It is sorted to facilitate set operations. 597 */ 598 private final Vector tvs = new Vector(); 599 600 void add(TypeAndValue tv) { 601 602 // Set i to index of first element greater than tv, or to 603 // tvs.size() if there is none. 604 int i; 605 for (i = 0; i < tvs.size(); i++) { 606 int diff = tv.compareTo(tvs.elementAt(i)); 607 if (diff == 0) { 608 return; // tv is a duplicate: ignore it 609 } else if (diff < 0) { 610 break; 611 } 612 } 613 614 tvs.insertElementAt(tv, i); 615 } 616 617 public String toString() { 618 StringBuffer buf = new StringBuffer(); 619 for (int i = 0; i < tvs.size(); i++) { 620 if (i > 0) { 621 buf.append('+'); 622 } 623 buf.append(tvs.elementAt(i)); 624 } 625 return new String(buf); 626 } 627 628 public boolean equals(Object obj) { 629 return ((obj instanceof Rdn) && 630 (compareTo(obj) == 0)); 631 } 632 633 // Compare TypeAndValue components one by one, lexicographically. 634 public int compareTo(Object obj) { 635 Rdn that = (Rdn)obj; 636 int minSize = Math.min(tvs.size(), that.tvs.size()); 637 for (int i = 0; i < minSize; i++) { 638 // Compare a single pair of type/value pairs. 639 TypeAndValue tv = (TypeAndValue)tvs.elementAt(i); 640 int diff = tv.compareTo(that.tvs.elementAt(i)); 641 if (diff != 0) { 642 return diff; 643 } 644 } 645 return (tvs.size() - that.tvs.size()); // longer RDN wins 646 } 647 648 public int hashCode() { 649 // Sum up the hash codes of the components. 650 int hash = 0; 651 652 // For each type/value pair... 653 for (int i = 0; i < tvs.size(); i++) { 654 hash += tvs.elementAt(i).hashCode(); 655 } 656 return hash; 657 } 658 659 Attributes toAttributes() { 660 Attributes attrs = new BasicAttributes(true); 661 TypeAndValue tv; 662 Attribute attr; 663 664 for (int i = 0; i < tvs.size(); i++) { 665 tv = (TypeAndValue) tvs.elementAt(i); 666 if ((attr = attrs.get(tv.getType())) == null) { 667 attrs.put(tv.getType(), tv.getUnescapedValue()); 668 } else { 669 attr.add(tv.getUnescapedValue()); 670 } 671 } 672 return attrs; 673 } 674 } 675 676 677 /* 678 * Class TypeAndValue represents an attribute type and its 679 * corresponding value. 680 */ 681 static class TypeAndValue { 682 683 private final String type; 684 private final String value; // value, escaped or quoted 685 private final boolean binary; 686 private final boolean valueCaseSensitive; 687 688 // If non-null, a canonical represention of the value suitable 689 // for comparison using String.compareTo(). 690 private String comparable = null; 691 692 TypeAndValue(String type, String value, boolean valueCaseSensitive) { 693 this.type = type; 694 this.value = value; 695 binary = value.startsWith("#"); 696 this.valueCaseSensitive = valueCaseSensitive; 697 } 698 699 public String toString() { 700 return (type + "=" + value); 701 } 702 703 public int compareTo(Object obj) { 704 // NB: Any change here affecting equality must be 705 // reflected in hashCode(). 706 707 TypeAndValue that = (TypeAndValue)obj; 708 709 int diff = type.toUpperCase().compareTo(that.type.toUpperCase()); 710 if (diff != 0) { 711 return diff; 712 } 713 if (value.equals(that.value)) { // try shortcut 714 return 0; 715 } 716 return getValueComparable().compareTo(that.getValueComparable()); 717 } 718 719 public boolean equals(Object obj) { 720 // NB: Any change here must be reflected in hashCode(). 721 if (!(obj instanceof TypeAndValue)) { 722 return false; 723 } 724 TypeAndValue that = (TypeAndValue)obj; 725 return (type.equalsIgnoreCase(that.type) && 726 (value.equals(that.value) || 727 getValueComparable().equals(that.getValueComparable()))); 728 } 729 730 public int hashCode() { 731 // If two objects are equal, their hash codes must match. 732 return (type.toUpperCase().hashCode() + 733 getValueComparable().hashCode()); 734 } 735 736 /* 737 * Returns the type. 738 */ 739 String getType() { 740 return type; 741 } 742 743 /* 744 * Returns the unescaped value. 745 */ 746 Object getUnescapedValue() { 747 return unescapeValue(value); 748 } 749 750 /* 751 * Returns a canonical representation of "value" suitable for 752 * comparison using String.compareTo(). If "value" is a string, 753 * it is returned with escapes and quotes stripped away, and 754 * hex-encoded UTF-8 converted to 16-bit Unicode chars. 755 * If value's case is to be ignored, it is returned in uppercase. 756 * If "value" is binary, it is returned in uppercase but 757 * otherwise unmodified. 758 */ 759 private String getValueComparable() { 760 if (comparable != null) { 761 return comparable; // return cached result 762 } 763 764 // cache result 765 if (binary) { 766 comparable = value.toUpperCase(); 767 } else { 768 comparable = (String)unescapeValue(value); 769 if (!valueCaseSensitive) { 770 comparable = comparable.toUpperCase(); // ignore case 771 } 772 } 773 return comparable; 774 } 775 776 /* 777 * Given the value of an attribute, returns a string suitable 778 * for inclusion in a DN. 779 */ 780 static String escapeValue(Object val) { 781 return (val instanceof byte[]) 782 ? escapeBinaryValue((byte[])val) 783 : escapeStringValue((String)val); 784 } 785 786 /* 787 * Given the value of a string-valued attribute, returns a 788 * string suitable for inclusion in a DN. This is accomplished by 789 * using backslash (\) to escape the following characters: 790 * leading and trailing whitespace 791 * , = + < > # ; " \ 792 */ 793 private static String escapeStringValue(String val) { 794 795 final String escapees = ",=+<>#;\"\\"; 796 char[] chars = val.toCharArray(); 797 StringBuffer buf = new StringBuffer(2 * val.length()); 798 799 // Find leading and trailing whitespace. 800 int lead; // index of first char that is not leading whitespace 801 for (lead = 0; lead < chars.length; lead++) { 802 if (!isWhitespace(chars[lead])) { 803 break; 804 } 805 } 806 int trail; // index of last char that is not trailing whitespace 807 for (trail = chars.length - 1; trail >= 0; trail--) { 808 if (!isWhitespace(chars[trail])) { 809 break; 810 } 811 } 812 813 for (int i = 0; i < chars.length; i++) { 814 char c = chars[i]; 815 if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) { 816 buf.append('\\'); 817 } 818 buf.append(c); 819 } 820 return new String(buf); 821 } 822 823 /* 824 * Given the value of a binary attribute, returns a string 825 * suitable for inclusion in a DN (such as "#CEB1DF80"). 826 */ 827 private static String escapeBinaryValue(byte[] val) { 828 829 StringBuffer buf = new StringBuffer(1 + 2 * val.length); 830 buf.append("#"); 831 832 for (int i = 0; i < val.length; i++) { 833 byte b = val[i]; 834 buf.append(Character.forDigit(0xF & (b >>> 4), 16)); 835 buf.append(Character.forDigit(0xF & b, 16)); 836 } 837 838 return (new String(buf)).toUpperCase(); 839 } 840 841 /* 842 * Given an attribute value formated according to RFC 2253, 843 * returns the unformated value. Escapes and quotes are 844 * stripped away, and hex-encoded UTF-8 is converted to 16-bit 845 * Unicode chars. Returns a string value as a String, and a 846 * binary value as a byte array. 847 */ 848 static Object unescapeValue(String val) { 849 850 char[] chars = val.toCharArray(); 851 int beg = 0; 852 int end = chars.length; 853 854 // Trim off leading and trailing whitespace. 855 while ((beg < end) && isWhitespace(chars[beg])) { 856 ++beg; 857 } 858 while ((beg < end) && isWhitespace(chars[end - 1])) { 859 --end; 860 } 861 862 // Add back the trailing whitespace with a preceeding '\' 863 // (escaped or unescaped) that was taken off in the above 864 // loop. Whether or not to retain this whitespace is 865 // decided below. 866 if (end != chars.length && 867 (beg < end) && 868 chars[end - 1] == '\\') { 869 end++; 870 } 871 if (beg >= end) { 872 return ""; 873 } 874 875 if (chars[beg] == '#') { 876 // Value is binary (eg: "#CEB1DF80"). 877 return decodeHexPairs(chars, ++beg, end); 878 } 879 880 // Trim off quotes. 881 if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) { 882 ++beg; 883 --end; 884 } 885 886 StringBuffer buf = new StringBuffer(end - beg); 887 int esc = -1; // index of the last escaped character 888 889 for (int i = beg; i < end; i++) { 890 if ((chars[i] == '\\') && (i + 1 < end)) { 891 if (!Character.isLetterOrDigit(chars[i + 1])) { 892 ++i; // skip backslash 893 buf.append(chars[i]); // snarf escaped char 894 esc = i; 895 } else { 896 897 // Convert hex-encoded UTF-8 to 16-bit chars. 898 byte[] utf8 = getUtf8Octets(chars, i, end); 899 if (utf8.length > 0) { 900 try { 901 buf.append(new String(utf8, "UTF8")); 902 } catch (java.io.UnsupportedEncodingException e) { 903 // shouldn't happen 904 } 905 i += utf8.length * 3 - 1; 906 } else { 907 throw new IllegalArgumentException( 908 "Not a valid attribute string value:" + 909 val +", improper usage of backslash"); 910 } 911 } 912 } else { 913 buf.append(chars[i]); // snarf unescaped char 914 } 915 } 916 917 // Get rid of the unescaped trailing whitespace with the 918 // preceeding '\' character that was previously added back. 919 int len = buf.length(); 920 if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) { 921 buf.setLength(len - 1); 922 } 923 924 return new String(buf); 925 } 926 927 928 /* 929 * Given an array of chars (with starting and ending indexes into it) 930 * representing bytes encoded as hex-pairs (such as "CEB1DF80"), 931 * returns a byte array containing the decoded bytes. 932 */ 933 private static byte[] decodeHexPairs(char[] chars, int beg, int end) { 934 byte[] bytes = new byte[(end - beg) / 2]; 935 for (int i = 0; beg + 1 < end; i++) { 936 int hi = Character.digit(chars[beg], 16); 937 int lo = Character.digit(chars[beg + 1], 16); 938 if (hi < 0 || lo < 0) { 939 break; 940 } 941 bytes[i] = (byte)((hi<<4) + lo); 942 beg += 2; 943 } 944 if (beg != end) { 945 throw new IllegalArgumentException( 946 "Illegal attribute value: #" + new String(chars)); 947 } 948 return bytes; 949 } 950 951 /* 952 * Given an array of chars (with starting and ending indexes into it), 953 * finds the largest prefix consisting of hex-encoded UTF-8 octets, 954 * and returns a byte array containing the corresponding UTF-8 octets. 955 * 956 * Hex-encoded UTF-8 octets look like this: 957 * \03\B1\DF\80 958 */ 959 private static byte[] getUtf8Octets(char[] chars, int beg, int end) { 960 byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room 961 int len = 0; // index of first unused byte in utf8 962 963 while ((beg + 2 < end) && 964 (chars[beg++] == '\\')) { 965 int hi = Character.digit(chars[beg++], 16); 966 int lo = Character.digit(chars[beg++], 16); 967 if (hi < 0 || lo < 0) { 968 break; 969 } 970 utf8[len++] = (byte)((hi<<4) + lo); 971 } 972 973 if (len == utf8.length) { 974 return utf8; 975 } else { 976 byte[] res = new byte[len]; 977 System.arraycopy(utf8, 0, res, 0, len); 978 return res; 979 } 980 } 981 } 982 983 984 /* 985 * For testing. 986 */ 987 /* 988 public static void main(String[] args) { 989 990 try { 991 if (args.length == 1) { // parse and print components 992 LdapName n = new LdapName(args[0]); 993 994 Enumeration rdns = n.rdns.elements(); 995 while (rdns.hasMoreElements()) { 996 Rdn rdn = (Rdn)rdns.nextElement(); 997 for (int i = 0; i < rdn.tvs.size(); i++) { 998 System.out.print("[" + rdn.tvs.elementAt(i) + "]"); 999 } 1000 System.out.println(); 1001 } 1002 1003 } else { // compare two names 1004 LdapName n1 = new LdapName(args[0]); 1005 LdapName n2 = new LdapName(args[1]); 1006 n1.unparsed = null; 1007 n2.unparsed = null; 1008 boolean eq = n1.equals(n2); 1009 System.out.println("[" + n1 + (eq ? "] == [" : "] != [") 1010 + n2 + "]"); 1011 } 1012 } catch (Exception e) { 1013 e.printStackTrace(); 1014 } 1015 } 1016 */ 1017 }