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