1 /* 2 * Copyright (c) 2003, 2020, 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.Locale; 32 import java.util.Collections; 33 34 import javax.naming.InvalidNameException; 35 import javax.naming.directory.BasicAttributes; 36 import javax.naming.directory.Attributes; 37 import javax.naming.directory.Attribute; 38 import javax.naming.NamingEnumeration; 39 import javax.naming.NamingException; 40 41 import java.io.Serializable; 42 import java.io.ObjectOutputStream; 43 import java.io.ObjectInputStream; 44 import java.io.IOException; 45 46 /** 47 * This class represents a relative distinguished name, or RDN, which is a 48 * component of a distinguished name as specified by 49 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>. 50 * An example of an RDN is "OU=Sales+CN=J.Smith". In this example, 51 * the RDN consist of multiple attribute type/value pairs. The 52 * RDN is parsed as described in the class description for 53 * {@link javax.naming.ldap.LdapName LdapName}. 54 * <p> 55 * The Rdn class represents an RDN as attribute type/value mappings, 56 * which can be viewed using 57 * {@link javax.naming.directory.Attributes Attributes}. 58 * In addition, it contains convenience methods that allow easy retrieval 59 * of type and value when the Rdn consist of a single type/value pair, 60 * which is how it appears in a typical usage. 61 * It also contains helper methods that allow escaping of the unformatted 62 * attribute value and unescaping of the value formatted according to the 63 * escaping syntax defined in RFC2253. For methods that take or return 64 * attribute value as an Object, the value is either a String 65 * (in unescaped form) or a byte array. 66 * <p> 67 * <code>Rdn</code> will properly parse all valid RDNs, but 68 * does not attempt to detect all possible violations when parsing 69 * invalid RDNs. It is "generous" in accepting invalid RDNs. 70 * The "validity" of a name is determined ultimately when it 71 * is supplied to an LDAP server, which may accept or 72 * reject the name based on factors such as its schema information 73 * and interoperability considerations. 74 * 75 * <p> 76 * The following code example shows how to construct an Rdn using the 77 * constructor that takes type and value as arguments: 78 * <pre> 79 * Rdn rdn = new Rdn("cn", "Juicy, Fruit"); 80 * System.out.println(rdn.toString()); 81 * </pre> 82 * The last line will print {@code cn=Juicy\, Fruit}. The 83 * {@link #unescapeValue(String) unescapeValue()} method can be 84 * used to unescape the escaped comma resulting in the original 85 * value {@code "Juicy, Fruit"}. The {@link #escapeValue(Object) 86 * escapeValue()} method adds the escape back preceding the comma. 87 * <p> 88 * This class can be instantiated by a string representation 89 * of the RDN defined in RFC 2253 as shown in the following code example: 90 * <pre> 91 * Rdn rdn = new Rdn("cn=Juicy\\, Fruit"); 92 * System.out.println(rdn.toString()); 93 * </pre> 94 * The last line will print {@code cn=Juicy\, Fruit}. 95 * <p> 96 * Concurrent multithreaded read-only access of an instance of 97 * {@code Rdn} need not be synchronized. 98 * <p> 99 * Unless otherwise noted, the behavior of passing a null argument 100 * to a constructor or method in this class will cause NullPointerException 101 * to be thrown. 102 * 103 * @since 1.5 104 */ 105 106 public class Rdn implements Serializable, Comparable<Object> { 107 108 private transient ArrayList<RdnEntry> 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 interpreted 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 {@code attrSet} 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<? extends Attribute> attrs = attrSet.getAll(); 135 try { 136 for (int nEntries = 0; attrs.hasMore(); nEntries++) { 137 RdnEntry entry = new RdnEntry(); 138 Attribute attr = 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 {@code rdn}. 170 * The contents of the {@code rdn} 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 interpreted 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 interpreted 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 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 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 */ 319 public int compareTo(Object obj) { 320 if (!(obj instanceof Rdn)) { 321 throw new ClassCastException("The obj is not a Rdn"); 322 } 323 if (obj == this) { 324 return 0; 325 } 326 Rdn that = (Rdn) obj; 327 int minSize = Math.min(entries.size(), that.entries.size()); 328 for (int i = 0; i < minSize; i++) { 329 330 // Compare a single pair of type/value pairs. 331 int diff = entries.get(i).compareTo(that.entries.get(i)); 332 if (diff != 0) { 333 return diff; 334 } 335 } 336 return (entries.size() - that.entries.size()); // longer RDN wins 337 } 338 339 /** 340 * Compares the specified Object with this Rdn for equality. 341 * Returns true if the given object is also a Rdn and the two Rdns 342 * represent the same attribute type and value mappings. The order of 343 * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not 344 * significant. 345 * <p> 346 * Type and value equality matching is done as below: 347 * <ul> 348 * <li> The types are compared for equality with their case ignored. 349 * <li> String values with different but equivalent usage of quoting, 350 * escaping, or UTF8-hex-encoding are considered equal. 351 * The case of the values is ignored during the comparison. 352 * </ul> 353 * <p> 354 * If obj is null or not an instance of Rdn, false is returned. 355 * 356 * @param obj object to be compared for equality with this Rdn. 357 * @return true if the specified object is equal to this Rdn. 358 * @see #hashCode() 359 */ 360 public boolean equals(Object obj) { 361 if (obj == this) { 362 return true; 363 } 364 if (!(obj instanceof Rdn)) { 365 return false; 366 } 367 Rdn that = (Rdn) obj; 368 if (entries.size() != that.size()) { 369 return false; 370 } 371 for (int i = 0; i < entries.size(); i++) { 372 if (!entries.get(i).equals(that.entries.get(i))) { 373 return false; 374 } 375 } 376 return true; 377 } 378 379 /** 380 * Returns the hash code of this RDN. Two RDNs that are 381 * equal (according to the equals method) will have the same 382 * hash code. 383 * 384 * @return An int representing the hash code of this Rdn. 385 * @see #equals 386 */ 387 public int hashCode() { 388 389 // Sum up the hash codes of the components. 390 int hash = 0; 391 392 // For each type/value pair... 393 for (int i = 0; i < entries.size(); i++) { 394 hash += entries.get(i).hashCode(); 395 } 396 return hash; 397 } 398 399 /** 400 * Retrieves the {@link javax.naming.directory.Attributes Attributes} 401 * view of the type/value mappings contained in this Rdn. 402 * 403 * @return The non-null attributes containing the type/value 404 * mappings of this Rdn. 405 */ 406 public Attributes toAttributes() { 407 Attributes attrs = new BasicAttributes(true); 408 for (int i = 0; i < entries.size(); i++) { 409 RdnEntry entry = entries.get(i); 410 Attribute attr = attrs.put(entry.getType(), entry.getValue()); 411 if (attr != null) { 412 attr.add(entry.getValue()); 413 attrs.put(attr); 414 } 415 } 416 return attrs; 417 } 418 419 420 private static class RdnEntry implements Comparable<RdnEntry> { 421 private String type; 422 private Object value; 423 424 // If non-null, a canonical representation of the value suitable 425 // for comparison using String.compareTo() 426 private String comparable = null; 427 428 String getType() { 429 return type; 430 } 431 432 Object getValue() { 433 return value; 434 } 435 436 public int compareTo(RdnEntry that) { 437 int diff = type.compareToIgnoreCase(that.type); 438 if (diff != 0) { 439 return diff; 440 } 441 if (value.equals(that.value)) { // try shortcut 442 return 0; 443 } 444 return getValueComparable().compareTo( 445 that.getValueComparable()); 446 } 447 448 public boolean equals(Object obj) { 449 if (obj == this) { 450 return true; 451 } 452 if (!(obj instanceof RdnEntry)) { 453 return false; 454 } 455 456 // Any change here must be reflected in hashCode() 457 RdnEntry that = (RdnEntry) obj; 458 return (type.equalsIgnoreCase(that.type)) && 459 (getValueComparable().equals( 460 that.getValueComparable())); 461 } 462 463 public int hashCode() { 464 return (type.toUpperCase(Locale.ENGLISH).hashCode() + 465 getValueComparable().hashCode()); 466 } 467 468 public String toString() { 469 return type + "=" + escapeValue(value); 470 } 471 472 private String getValueComparable() { 473 if (comparable != null) { 474 return comparable; // return cached result 475 } 476 477 // cache result 478 if (value instanceof byte[]) { 479 comparable = escapeBinaryValue((byte[]) value); 480 } else { 481 comparable = ((String) value).toUpperCase(Locale.ENGLISH); 482 } 483 return comparable; 484 } 485 } 486 487 /** 488 * Retrieves the number of attribute type/value pairs in this Rdn. 489 * @return The non-negative number of type/value pairs in this Rdn. 490 */ 491 public int size() { 492 return entries.size(); 493 } 494 495 /** 496 * Given the value of an attribute, returns a string escaped according 497 * to the rules specified in 498 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>. 499 * <p> 500 * For example, if the val is "Sue, Grabbit and Runn", the escaped 501 * value returned by this method is "Sue\, Grabbit and Runn". 502 * <p> 503 * A string value is represented as a String and binary value 504 * as a byte array. 505 * 506 * @param val The non-null object to be escaped. 507 * @return Escaped string value. 508 * @throws ClassCastException if val is not a String or byte array. 509 */ 510 public static String escapeValue(Object val) { 511 return (val instanceof byte[]) 512 ? escapeBinaryValue((byte[])val) 513 : escapeStringValue((String)val); 514 } 515 516 /* 517 * Given the value of a string-valued attribute, returns a 518 * string suitable for inclusion in a DN. This is accomplished by 519 * using backslash (\) to escape the following characters: 520 * leading and trailing whitespace 521 * , = + < > # ; " \ 522 */ 523 private static final String escapees = ",=+<>#;\"\\"; 524 525 private static String escapeStringValue(String val) { 526 527 char[] chars = val.toCharArray(); 528 StringBuilder builder = new StringBuilder(2 * val.length()); 529 530 // Find leading and trailing whitespace. 531 int lead; // index of first char that is not leading whitespace 532 for (lead = 0; lead < chars.length; lead++) { 533 if (!isWhitespace(chars[lead])) { 534 break; 535 } 536 } 537 int trail; // index of last char that is not trailing whitespace 538 for (trail = chars.length - 1; trail >= 0; trail--) { 539 if (!isWhitespace(chars[trail])) { 540 break; 541 } 542 } 543 544 for (int i = 0; i < chars.length; i++) { 545 char c = chars[i]; 546 if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) { 547 builder.append('\\'); 548 } 549 builder.append(c); 550 } 551 return builder.toString(); 552 } 553 554 /* 555 * Given the value of a binary attribute, returns a string 556 * suitable for inclusion in a DN (such as "#CEB1DF80"). 557 * TBD: This method should actually generate the ber encoding 558 * of the binary value 559 */ 560 private static String escapeBinaryValue(byte[] val) { 561 562 StringBuilder builder = new StringBuilder(1 + 2 * val.length); 563 builder.append("#"); 564 565 for (int i = 0; i < val.length; i++) { 566 byte b = val[i]; 567 builder.append(Character.forDigit(0xF & (b >>> 4), 16)); 568 builder.append(Character.forDigit(0xF & b, 16)); 569 } 570 return builder.toString(); 571 } 572 573 /** 574 * Given an attribute value string formatted according to the rules 575 * specified in 576 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>, 577 * returns the unformatted value. Escapes and quotes are 578 * stripped away, and hex-encoded UTF-8 is converted to equivalent 579 * UTF-16 characters. Returns a string value as a String, and a 580 * binary value as a byte array. 581 * <p> 582 * Legal and illegal values are defined in RFC 2253. 583 * This method is generous in accepting the values and does not 584 * catch all illegal values. 585 * Therefore, passing in an illegal value might not necessarily 586 * trigger an {@code IllegalArgumentException}. 587 * 588 * @param val The non-null string to be unescaped. 589 * @return Unescaped value. 590 * @throws IllegalArgumentException When an Illegal value 591 * is provided. 592 */ 593 public static Object unescapeValue(String val) { 594 595 char[] chars = val.toCharArray(); 596 int beg = 0; 597 int end = chars.length; 598 599 // Trim off leading and trailing whitespace. 600 while ((beg < end) && isWhitespace(chars[beg])) { 601 ++beg; 602 } 603 604 while ((beg < end) && isWhitespace(chars[end - 1])) { 605 --end; 606 } 607 608 // Add back the trailing whitespace with a preceding '\' 609 // (escaped or unescaped) that was taken off in the above 610 // loop. Whether or not to retain this whitespace is decided below. 611 if (end != chars.length && 612 (beg < end) && 613 chars[end - 1] == '\\') { 614 end++; 615 } 616 if (beg >= end) { 617 return ""; 618 } 619 620 if (chars[beg] == '#') { 621 // Value is binary (eg: "#CEB1DF80"). 622 return decodeHexPairs(chars, ++beg, end); 623 } 624 625 // Trim off quotes. 626 if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) { 627 ++beg; 628 --end; 629 } 630 631 StringBuilder builder = new StringBuilder(end - beg); 632 int esc = -1; // index of the last escaped character 633 634 for (int i = beg; i < end; i++) { 635 if ((chars[i] == '\\') && (i + 1 < end)) { 636 if (!Character.isLetterOrDigit(chars[i + 1])) { 637 ++i; // skip backslash 638 builder.append(chars[i]); // snarf escaped char 639 esc = i; 640 } else { 641 642 // Convert hex-encoded UTF-8 to 16-bit chars. 643 byte[] utf8 = getUtf8Octets(chars, i, end); 644 if (utf8.length > 0) { 645 try { 646 builder.append(new String(utf8, "UTF8")); 647 } catch (java.io.UnsupportedEncodingException e) { 648 // shouldn't happen 649 } 650 i += utf8.length * 3 - 1; 651 } else { // no utf8 bytes available, invalid DN 652 653 // '/' has no meaning, throw exception 654 throw new IllegalArgumentException( 655 "Not a valid attribute string value:" + 656 val + ",improper usage of backslash"); 657 } 658 } 659 } else { 660 builder.append(chars[i]); // snarf unescaped char 661 } 662 } 663 664 // Get rid of the unescaped trailing whitespace with the 665 // preceding '\' character that was previously added back. 666 int len = builder.length(); 667 if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) { 668 builder.setLength(len - 1); 669 } 670 return builder.toString(); 671 } 672 673 674 /* 675 * Given an array of chars (with starting and ending indexes into it) 676 * representing bytes encoded as hex-pairs (such as "CEB1DF80"), 677 * returns a byte array containing the decoded bytes. 678 */ 679 private static byte[] decodeHexPairs(char[] chars, int beg, int end) { 680 byte[] bytes = new byte[(end - beg) / 2]; 681 for (int i = 0; beg + 1 < end; i++) { 682 int hi = Character.digit(chars[beg], 16); 683 int lo = Character.digit(chars[beg + 1], 16); 684 if (hi < 0 || lo < 0) { 685 break; 686 } 687 bytes[i] = (byte)((hi<<4) + lo); 688 beg += 2; 689 } 690 if (beg != end) { 691 throw new IllegalArgumentException( 692 "Illegal attribute value: " + new String(chars)); 693 } 694 return bytes; 695 } 696 697 /* 698 * Given an array of chars (with starting and ending indexes into it), 699 * finds the largest prefix consisting of hex-encoded UTF-8 octets, 700 * and returns a byte array containing the corresponding UTF-8 octets. 701 * 702 * Hex-encoded UTF-8 octets look like this: 703 * \03\B1\DF\80 704 */ 705 private static byte[] getUtf8Octets(char[] chars, int beg, int end) { 706 byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room 707 int len = 0; // index of first unused byte in utf8 708 709 while ((beg + 2 < end) && 710 (chars[beg++] == '\\')) { 711 int hi = Character.digit(chars[beg++], 16); 712 int lo = Character.digit(chars[beg++], 16); 713 if (hi < 0 || lo < 0) { 714 break; 715 } 716 utf8[len++] = (byte)((hi<<4) + lo); 717 } 718 if (len == utf8.length) { 719 return utf8; 720 } else { 721 byte[] res = new byte[len]; 722 System.arraycopy(utf8, 0, res, 0, len); 723 return res; 724 } 725 } 726 727 /* 728 * Best guess as to what RFC 2253 means by "whitespace". 729 */ 730 private static boolean isWhitespace(char c) { 731 return (c == ' ' || c == '\r'); 732 } 733 734 /** 735 * Serializes only the unparsed RDN, for compactness and to avoid 736 * any implementation dependency. 737 * 738 * @serialData The unparsed RDN {@code String} representation. 739 * 740 * @param s the {@code ObjectOutputStream} to write to 741 * @throws java.io.IOException if an I/O error occurs. 742 */ 743 @java.io.Serial 744 private void writeObject(ObjectOutputStream s) 745 throws java.io.IOException { 746 s.defaultWriteObject(); 747 s.writeObject(toString()); 748 } 749 750 /** 751 * Initializes the {@code Rdn} from deserialized data. 752 * 753 * See {@code writeObject} for a description of the serial form. 754 * 755 * @param s the {@code ObjectInputStream} to read from 756 * @throws IOException if an I/O error occurs. 757 * @throws ClassNotFoundException if the class of a serialized object 758 * could not be found. 759 */ 760 @java.io.Serial 761 private void readObject(ObjectInputStream s) 762 throws IOException, ClassNotFoundException { 763 s.defaultReadObject(); 764 entries = new ArrayList<>(DEFAULT_SIZE); 765 String unparsed = (String) s.readObject(); 766 try { 767 (new Rfc2253Parser(unparsed)).parseRdn(this); 768 } catch (InvalidNameException e) { 769 // shouldn't happen 770 throw new java.io.StreamCorruptedException( 771 "Invalid name: " + unparsed); 772 } 773 } 774 }