1 /* 2 * Copyright (c) 2003, 2010, 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 javax.naming.ldap; 27 28 import java.util.Iterator; 29 import java.util.NoSuchElementException; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 33 import javax.naming.InvalidNameException; 34 import javax.naming.directory.BasicAttributes; 35 import javax.naming.directory.Attributes; 36 import javax.naming.directory.Attribute; 37 import javax.naming.NamingEnumeration; 38 import javax.naming.NamingException; 39 40 import java.io.Serializable; 41 import java.io.ObjectOutputStream; 42 import java.io.ObjectInputStream; 43 import java.io.IOException; 44 45 /** 46 * This class represents a relative distinguished name, or RDN, which is a 47 * component of a distinguished name as specified by 48 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>. 49 * An example of an RDN is "OU=Sales+CN=J.Smith". In this example, 50 * the RDN consist of multiple attribute type/value pairs. The 51 * RDN is parsed as described in the class description for 52 * {@link javax.naming.ldap.LdapName <tt>LdapName</tt>}. 53 * <p> 54 * The Rdn class represents an RDN as attribute type/value mappings, 55 * which can be viewed using 56 * {@link javax.naming.directory.Attributes Attributes}. 57 * In addition, it contains convenience methods that allow easy retrieval 58 * of type and value when the Rdn consist of a single type/value pair, 59 * which is how it appears in a typical usage. 60 * It also contains helper methods that allow escaping of the unformatted 61 * attribute value and unescaping of the value formatted according to the 62 * escaping syntax defined in RFC2253. For methods that take or return 63 * attribute value as an Object, the value is either a String 64 * (in unescaped form) or a byte array. 65 * <p> 66 * <code>Rdn</code> will properly parse all valid RDNs, but 67 * does not attempt to detect all possible violations when parsing 68 * invalid RDNs. It is "generous" in accepting invalid RDNs. 69 * The "validity" of a name is determined ultimately when it 70 * is supplied to an LDAP server, which may accept or 71 * reject the name based on factors such as its schema information 72 * and interoperability considerations. 73 * 74 * <p> 75 * The following code example shows how to construct an Rdn using the 76 * constructor that takes type and value as arguments: 77 * <pre> 78 * Rdn rdn = new Rdn("cn", "Juicy, Fruit"); 79 * System.out.println(rdn.toString()); 80 * </pre> 81 * The last line will print <tt>cn=Juicy\, Fruit</tt>. The 82 * {@link #unescapeValue(String) <tt>unescapeValue()</tt>} method can be 83 * used to unescape the escaped comma resulting in the original 84 * value <tt>"Juicy, Fruit"</tt>. The {@link #escapeValue(Object) 85 * <tt>escapeValue()</tt>} method adds the escape back preceding the comma. 86 * <p> 87 * This class can be instantiated by a string representation 88 * of the RDN defined in RFC 2253 as shown in the following code example: 89 * <pre> 90 * Rdn rdn = new Rdn("cn=Juicy\\, Fruit"); 91 * System.out.println(rdn.toString()); 92 * </pre> 93 * The last line will print <tt>cn=Juicy\, Fruit</tt>. 94 * <p> 95 * Concurrent multithreaded read-only access of an instance of 96 * <tt>Rdn</tt> need not be synchronized. 97 * <p> 98 * Unless otherwise noted, the behavior of passing a null argument 99 * to a constructor or method in this class will cause NullPointerException 100 * to be thrown. 101 * 102 * @since 1.5 103 */ 104 105 public class Rdn implements Serializable, Comparable<Object> { 106 107 // private transient ArrayList<RdnEntry> entries; 108 private transient ArrayList entries; 109 110 // The common case. 111 private static final int DEFAULT_SIZE = 1; 112 113 private static final long serialVersionUID = -5994465067210009656L; 114 115 /** 116 * Constructs an Rdn from the given attribute set. See 117 * {@link javax.naming.directory.Attributes Attributes}. 118 * <p> 119 * The string attribute values are not interpretted as 120 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> 121 * formatted RDN strings. That is, the values are used 122 * literally (not parsed) and assumed to be unescaped. 123 * 124 * @param attrSet The non-null and non-empty attributes containing 125 * type/value mappings. 126 * @throws InvalidNameException If contents of <tt>attrSet</tt> cannot 127 * be used to construct a valid RDN. 128 */ 129 public Rdn(Attributes attrSet) throws InvalidNameException { 130 if (attrSet.size() == 0) { 131 throw new InvalidNameException("Attributes cannot be empty"); 132 } 133 entries = new ArrayList(attrSet.size()); 134 NamingEnumeration attrs = attrSet.getAll(); 135 try { 136 for (int nEntries = 0; attrs.hasMore(); nEntries++) { 137 RdnEntry entry = new RdnEntry(); 138 Attribute attr = (Attribute) attrs.next(); 139 entry.type = attr.getID(); 140 entry.value = attr.get(); 141 entries.add(nEntries, entry); 142 } 143 } catch (NamingException e) { 144 InvalidNameException e2 = new InvalidNameException( 145 e.getMessage()); 146 e2.initCause(e); 147 throw e2; 148 } 149 sort(); // arrange entries for comparison 150 } 151 152 /** 153 * Constructs an Rdn from the given string. 154 * This constructor takes a string formatted according to the rules 155 * defined in <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> 156 * and described in the class description for 157 * {@link javax.naming.ldap.LdapName}. 158 * 159 * @param rdnString The non-null and non-empty RFC2253 formatted string. 160 * @throws InvalidNameException If a syntax error occurs during 161 * parsing of the rdnString. 162 */ 163 public Rdn(String rdnString) throws InvalidNameException { 164 entries = new ArrayList(DEFAULT_SIZE); 165 (new Rfc2253Parser(rdnString)).parseRdn(this); 166 } 167 168 /** 169 * Constructs an Rdn from the given <tt>rdn</tt>. 170 * The contents of the <tt>rdn</tt> are simply copied into the newly 171 * created Rdn. 172 * @param rdn The non-null Rdn to be copied. 173 */ 174 public Rdn(Rdn rdn) { 175 entries = new ArrayList(rdn.entries.size()); 176 entries.addAll(rdn.entries); 177 } 178 179 /** 180 * Constructs an Rdn from the given attribute type and 181 * value. 182 * The string attribute values are not interpretted as 183 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> 184 * formatted RDN strings. That is, the values are used 185 * literally (not parsed) and assumed to be unescaped. 186 * 187 * @param type The non-null and non-empty string attribute type. 188 * @param value The non-null and non-empty attribute value. 189 * @throws InvalidNameException If type/value cannot be used to 190 * construct a valid RDN. 191 * @see #toString() 192 */ 193 public Rdn(String type, Object value) throws InvalidNameException { 194 if (value == null) { 195 throw new NullPointerException("Cannot set value to null"); 196 } 197 if (type.equals("") || isEmptyValue(value)) { 198 throw new InvalidNameException( 199 "type or value cannot be empty, type:" + type + 200 " value:" + value); 201 } 202 entries = new ArrayList(DEFAULT_SIZE); 203 put(type, value); 204 } 205 206 private boolean isEmptyValue(Object val) { 207 return ((val instanceof String) && val.equals("")) || 208 ((val instanceof byte[]) && (((byte[]) val).length == 0)); 209 } 210 211 // An empty constructor used by the parser 212 Rdn() { 213 entries = new ArrayList(DEFAULT_SIZE); 214 } 215 216 /* 217 * Adds the given attribute type and value to this Rdn. 218 * The string attribute values are not interpretted as 219 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> 220 * formatted RDN strings. That is the values are used 221 * literally (not parsed) and assumed to be unescaped. 222 * 223 * @param type The non-null and non-empty string attribute type. 224 * @param value The non-null and non-empty attribute value. 225 * @return The updated Rdn, not a new one. Cannot be null. 226 * @see #toString() 227 */ 228 Rdn put(String type, Object value) { 229 230 // create new Entry 231 RdnEntry newEntry = new RdnEntry(); 232 newEntry.type = type; 233 if (value instanceof byte[]) { // clone the byte array 234 newEntry.value = ((byte[]) value).clone(); 235 } else { 236 newEntry.value = value; 237 } 238 entries.add(newEntry); 239 return this; 240 } 241 242 void sort() { 243 if (entries.size() > 1) { 244 Collections.sort(entries); 245 } 246 } 247 248 /** 249 * Retrieves one of this Rdn's value. 250 * This is a convenience method for obtaining the value, 251 * when the RDN contains a single type and value mapping, 252 * which is the common RDN usage. 253 * <p> 254 * For a multi-valued RDN, this method returns value corresponding 255 * to the type returned by {@link #getType() getType()} method. 256 * 257 * @return The non-null attribute value. 258 */ 259 public Object getValue() { 260 return ((RdnEntry) entries.get(0)).getValue(); 261 } 262 263 /** 264 * Retrieves one of this Rdn's type. 265 * This is a convenience method for obtaining the type, 266 * when the RDN contains a single type and value mapping, 267 * which is the common RDN usage. 268 * <p> 269 * For a multi-valued RDN, the type/value pairs have 270 * no specific order defined on them. In that case, this method 271 * returns type of one of the type/value pairs. 272 * The {@link #getValue() getValue()} method returns the 273 * value corresponding to the type returned by this method. 274 * 275 * @return The non-null attribute type. 276 */ 277 public String getType() { 278 return ((RdnEntry) entries.get(0)).getType(); 279 } 280 281 /** 282 * Returns this Rdn as a string represented in a format defined by 283 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> and described 284 * in the class description for {@link javax.naming.ldap.LdapName LdapName}. 285 * 286 * @return The string representation of the Rdn. 287 */ 288 public String toString() { 289 StringBuilder builder = new StringBuilder(); 290 int size = entries.size(); 291 if (size > 0) { 292 builder.append(entries.get(0)); 293 } 294 for (int next = 1; next < size; next++) { 295 builder.append('+'); 296 builder.append(entries.get(next)); 297 } 298 return builder.toString(); 299 } 300 301 /** 302 * Compares this Rdn with the specified Object for order. 303 * Returns a negative integer, zero, or a positive integer as this 304 * Rdn is less than, equal to, or greater than the given Object. 305 * <p> 306 * If obj is null or not an instance of Rdn, ClassCastException 307 * is thrown. 308 * <p> 309 * The attribute type and value pairs of the RDNs are lined up 310 * against each other and compared lexicographically. The order of 311 * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not 312 * significant. 313 * 314 * @param obj The non-null object to compare against. 315 * @return A negative integer, zero, or a positive integer as this Rdn 316 * is less than, equal to, or greater than the given Object. 317 * @exception ClassCastException if obj is null or not a Rdn. 318 * <p> 319 */ 320 public int compareTo(Object obj) { 321 if (!(obj instanceof Rdn)) { 322 throw new ClassCastException("The obj is not a Rdn"); 323 } 324 if (obj == this) { 325 return 0; 326 } 327 Rdn that = (Rdn) obj; 328 int minSize = Math.min(entries.size(), that.entries.size()); 329 for (int i = 0; i < minSize; i++) { 330 331 // Compare a single pair of type/value pairs. 332 int diff = ((RdnEntry) entries.get(i)).compareTo( 333 that.entries.get(i)); 334 if (diff != 0) { 335 return diff; 336 } 337 } 338 return (entries.size() - that.entries.size()); // longer RDN wins 339 } 340 341 /** 342 * Compares the specified Object with this Rdn for equality. 343 * Returns true if the given object is also a Rdn and the two Rdns 344 * represent the same attribute type and value mappings. The order of 345 * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not 346 * significant. 347 * <p> 348 * Type and value equalilty matching is done as below: 349 * <ul> 350 * <li> The types are compared for equality with their case ignored. 351 * <li> String values with different but equivalent usage of quoting, 352 * escaping, or UTF8-hex-encoding are considered equal. 353 * The case of the values is ignored during the comparison. 354 * </ul> 355 * <p> 356 * If obj is null or not an instance of Rdn, false is returned. 357 * <p> 358 * @param obj object to be compared for equality with this Rdn. 359 * @return true if the specified object is equal to this Rdn. 360 * @see #hashCode() 361 */ 362 public boolean equals(Object obj) { 363 if (obj == this) { 364 return true; 365 } 366 if (!(obj instanceof Rdn)) { 367 return false; 368 } 369 Rdn that = (Rdn) obj; 370 if (entries.size() != that.size()) { 371 return false; 372 } 373 for (int i = 0; i < entries.size(); i++) { 374 if (!entries.get(i).equals(that.entries.get(i))) { 375 return false; 376 } 377 } 378 return true; 379 } 380 381 /** 382 * Returns the hash code of this RDN. Two RDNs that are 383 * equal (according to the equals method) will have the same 384 * hash code. 385 * 386 * @return An int representing the hash code of this Rdn. 387 * @see #equals 388 */ 389 public int hashCode() { 390 391 // Sum up the hash codes of the components. 392 int hash = 0; 393 394 // For each type/value pair... 395 for (int i = 0; i < entries.size(); i++) { 396 hash += entries.get(i).hashCode(); 397 } 398 return hash; 399 } 400 401 /** 402 * Retrieves the {@link javax.naming.directory.Attributes Attributes} 403 * view of the type/value mappings contained in this Rdn. 404 * 405 * @return The non-null attributes containing the type/value 406 * mappings of this Rdn. 407 */ 408 public Attributes toAttributes() { 409 Attributes attrs = new BasicAttributes(true); 410 for (int i = 0; i < entries.size(); i++) { 411 RdnEntry entry = (RdnEntry) entries.get(i); 412 Attribute attr = attrs.put(entry.getType(), entry.getValue()); 413 if (attr != null) { 414 attr.add(entry.getValue()); 415 attrs.put(attr); 416 } 417 } 418 return attrs; 419 } 420 421 422 private static class RdnEntry implements Comparable { 423 private String type; 424 private Object value; 425 426 // If non-null, a cannonical representation of the value suitable 427 // for comparison using String.compareTo() 428 private String comparable = null; 429 430 String getType() { 431 return type; 432 } 433 434 Object getValue() { 435 return value; 436 } 437 438 public int compareTo(Object obj) { 439 440 // Any change here affecting equality must be 441 // reflected in hashCode(). 442 RdnEntry that = (RdnEntry) obj; 443 444 int diff = type.toUpperCase().compareTo( 445 that.type.toUpperCase()); 446 if (diff != 0) { 447 return diff; 448 } 449 if (value.equals(that.value)) { // try shortcut 450 return 0; 451 } 452 return getValueComparable().compareTo( 453 that.getValueComparable()); 454 } 455 456 public boolean equals(Object obj) { 457 if (obj == this) { 458 return true; 459 } 460 if (!(obj instanceof RdnEntry)) { 461 return false; 462 } 463 464 // Any change here must be reflected in hashCode() 465 RdnEntry that = (RdnEntry) obj; 466 return (type.equalsIgnoreCase(that.type)) && 467 (getValueComparable().equals( 468 that.getValueComparable())); 469 } 470 471 public int hashCode() { 472 return (type.toUpperCase().hashCode() + 473 getValueComparable().hashCode()); 474 } 475 476 public String toString() { 477 return type + "=" + escapeValue(value); 478 } 479 480 private String getValueComparable() { 481 if (comparable != null) { 482 return comparable; // return cached result 483 } 484 485 // cache result 486 if (value instanceof byte[]) { 487 comparable = escapeBinaryValue((byte[]) value); 488 } else { 489 comparable = ((String) value).toUpperCase(); 490 } 491 return comparable; 492 } 493 } 494 495 /** 496 * Retrieves the number of attribute type/value pairs in this Rdn. 497 * @return The non-negative number of type/value pairs in this Rdn. 498 */ 499 public int size() { 500 return entries.size(); 501 } 502 503 /** 504 * Given the value of an attribute, returns a string escaped according 505 * to the rules specified in 506 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>. 507 * <p> 508 * For example, if the val is "Sue, Grabbit and Runn", the escaped 509 * value returned by this method is "Sue\, Grabbit and Runn". 510 * <p> 511 * A string value is represented as a String and binary value 512 * as a byte array. 513 * 514 * @param val The non-null object to be escaped. 515 * @return Escaped string value. 516 * @throws ClassCastException if val is is not a String or byte array. 517 */ 518 public static String escapeValue(Object val) { 519 return (val instanceof byte[]) 520 ? escapeBinaryValue((byte[])val) 521 : escapeStringValue((String)val); 522 } 523 524 /* 525 * Given the value of a string-valued attribute, returns a 526 * string suitable for inclusion in a DN. This is accomplished by 527 * using backslash (\) to escape the following characters: 528 * leading and trailing whitespace 529 * , = + < > # ; " \ 530 */ 531 private static final String escapees = ",=+<>#;\"\\"; 532 533 private static String escapeStringValue(String val) { 534 535 char[] chars = val.toCharArray(); 536 StringBuilder builder = new StringBuilder(2 * val.length()); 537 538 // Find leading and trailing whitespace. 539 int lead; // index of first char that is not leading whitespace 540 for (lead = 0; lead < chars.length; lead++) { 541 if (!isWhitespace(chars[lead])) { 542 break; 543 } 544 } 545 int trail; // index of last char that is not trailing whitespace 546 for (trail = chars.length - 1; trail >= 0; trail--) { 547 if (!isWhitespace(chars[trail])) { 548 break; 549 } 550 } 551 552 for (int i = 0; i < chars.length; i++) { 553 char c = chars[i]; 554 if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) { 555 builder.append('\\'); 556 } 557 builder.append(c); 558 } 559 return builder.toString(); 560 } 561 562 /* 563 * Given the value of a binary attribute, returns a string 564 * suitable for inclusion in a DN (such as "#CEB1DF80"). 565 * TBD: This method should actually generate the ber encoding 566 * of the binary value 567 */ 568 private static String escapeBinaryValue(byte[] val) { 569 570 StringBuilder builder = new StringBuilder(1 + 2 * val.length); 571 builder.append("#"); 572 573 for (int i = 0; i < val.length; i++) { 574 byte b = val[i]; 575 builder.append(Character.forDigit(0xF & (b >>> 4), 16)); 576 builder.append(Character.forDigit(0xF & b, 16)); 577 } 578 return builder.toString(); 579 // return builder.toString().toUpperCase(); 580 } 581 582 /** 583 * Given an attribute value string formated according to the rules 584 * specified in 585 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>, 586 * returns the unformated value. Escapes and quotes are 587 * stripped away, and hex-encoded UTF-8 is converted to equivalent 588 * UTF-16 characters. Returns a string value as a String, and a 589 * binary value as a byte array. 590 * <p> 591 * Legal and illegal values are defined in RFC 2253. 592 * This method is generous in accepting the values and does not 593 * catch all illegal values. 594 * Therefore, passing in an illegal value might not necessarily 595 * trigger an <tt>IllegalArgumentException</tt>. 596 * 597 * @param val The non-null string to be unescaped. 598 * @return Unescaped value. 599 * @throws IllegalArgumentException When an Illegal value 600 * is provided. 601 */ 602 public static Object unescapeValue(String val) { 603 604 char[] chars = val.toCharArray(); 605 int beg = 0; 606 int end = chars.length; 607 608 // Trim off leading and trailing whitespace. 609 while ((beg < end) && isWhitespace(chars[beg])) { 610 ++beg; 611 } 612 613 while ((beg < end) && isWhitespace(chars[end - 1])) { 614 --end; 615 } 616 617 // Add back the trailing whitespace with a preceeding '\' 618 // (escaped or unescaped) that was taken off in the above 619 // loop. Whether or not to retain this whitespace is decided below. 620 if (end != chars.length && 621 (beg < end) && 622 chars[end - 1] == '\\') { 623 end++; 624 } 625 if (beg >= end) { 626 return ""; 627 } 628 629 if (chars[beg] == '#') { 630 // Value is binary (eg: "#CEB1DF80"). 631 return decodeHexPairs(chars, ++beg, end); 632 } 633 634 // Trim off quotes. 635 if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) { 636 ++beg; 637 --end; 638 } 639 640 StringBuilder builder = new StringBuilder(end - beg); 641 int esc = -1; // index of the last escaped character 642 643 for (int i = beg; i < end; i++) { 644 if ((chars[i] == '\\') && (i + 1 < end)) { 645 if (!Character.isLetterOrDigit(chars[i + 1])) { 646 ++i; // skip backslash 647 builder.append(chars[i]); // snarf escaped char 648 esc = i; 649 } else { 650 651 // Convert hex-encoded UTF-8 to 16-bit chars. 652 byte[] utf8 = getUtf8Octets(chars, i, end); 653 if (utf8.length > 0) { 654 try { 655 builder.append(new String(utf8, "UTF8")); 656 } catch (java.io.UnsupportedEncodingException e) { 657 // shouldn't happen 658 } 659 i += utf8.length * 3 - 1; 660 } else { // no utf8 bytes available, invalid DN 661 662 // '/' has no meaning, throw exception 663 throw new IllegalArgumentException( 664 "Not a valid attribute string value:" + 665 val + ",improper usage of backslash"); 666 } 667 } 668 } else { 669 builder.append(chars[i]); // snarf unescaped char 670 } 671 } 672 673 // Get rid of the unescaped trailing whitespace with the 674 // preceeding '\' character that was previously added back. 675 int len = builder.length(); 676 if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) { 677 builder.setLength(len - 1); 678 } 679 return builder.toString(); 680 } 681 682 683 /* 684 * Given an array of chars (with starting and ending indexes into it) 685 * representing bytes encoded as hex-pairs (such as "CEB1DF80"), 686 * returns a byte array containing the decoded bytes. 687 */ 688 private static byte[] decodeHexPairs(char[] chars, int beg, int end) { 689 byte[] bytes = new byte[(end - beg) / 2]; 690 for (int i = 0; beg + 1 < end; i++) { 691 int hi = Character.digit(chars[beg], 16); 692 int lo = Character.digit(chars[beg + 1], 16); 693 if (hi < 0 || lo < 0) { 694 break; 695 } 696 bytes[i] = (byte)((hi<<4) + lo); 697 beg += 2; 698 } 699 if (beg != end) { 700 throw new IllegalArgumentException( 701 "Illegal attribute value: " + new String(chars)); 702 } 703 return bytes; 704 } 705 706 /* 707 * Given an array of chars (with starting and ending indexes into it), 708 * finds the largest prefix consisting of hex-encoded UTF-8 octets, 709 * and returns a byte array containing the corresponding UTF-8 octets. 710 * 711 * Hex-encoded UTF-8 octets look like this: 712 * \03\B1\DF\80 713 */ 714 private static byte[] getUtf8Octets(char[] chars, int beg, int end) { 715 byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room 716 int len = 0; // index of first unused byte in utf8 717 718 while ((beg + 2 < end) && 719 (chars[beg++] == '\\')) { 720 int hi = Character.digit(chars[beg++], 16); 721 int lo = Character.digit(chars[beg++], 16); 722 if (hi < 0 || lo < 0) { 723 break; 724 } 725 utf8[len++] = (byte)((hi<<4) + lo); 726 } 727 if (len == utf8.length) { 728 return utf8; 729 } else { 730 byte[] res = new byte[len]; 731 System.arraycopy(utf8, 0, res, 0, len); 732 return res; 733 } 734 } 735 736 /* 737 * Best guess as to what RFC 2253 means by "whitespace". 738 */ 739 private static boolean isWhitespace(char c) { 740 return (c == ' ' || c == '\r'); 741 } 742 743 /** 744 * Serializes only the unparsed RDN, for compactness and to avoid 745 * any implementation dependency. 746 * 747 * @serialData The RDN string 748 */ 749 private void writeObject(ObjectOutputStream s) 750 throws java.io.IOException { 751 s.defaultWriteObject(); 752 s.writeObject(toString()); 753 } 754 755 private void readObject(ObjectInputStream s) 756 throws IOException, ClassNotFoundException { 757 s.defaultReadObject(); 758 entries = new ArrayList(DEFAULT_SIZE); 759 String unparsed = (String) s.readObject(); 760 try { 761 (new Rfc2253Parser(unparsed)).parseRdn(this); 762 } catch (InvalidNameException e) { 763 // shouldn't happen 764 throw new java.io.StreamCorruptedException( 765 "Invalid name: " + unparsed); 766 } 767 } 768 }