0N/A/*
3261N/A * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage javax.naming.ldap;
0N/A
0N/Aimport java.util.Iterator;
0N/Aimport java.util.NoSuchElementException;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.Collections;
0N/A
0N/Aimport javax.naming.InvalidNameException;
0N/Aimport javax.naming.directory.BasicAttributes;
0N/Aimport javax.naming.directory.Attributes;
0N/Aimport javax.naming.directory.Attribute;
0N/Aimport javax.naming.NamingEnumeration;
0N/Aimport javax.naming.NamingException;
0N/A
0N/Aimport java.io.Serializable;
0N/Aimport java.io.ObjectOutputStream;
0N/Aimport java.io.ObjectInputStream;
0N/Aimport java.io.IOException;
0N/A
0N/A/**
0N/A * This class represents a relative distinguished name, or RDN, which is a
0N/A * component of a distinguished name as specified by
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
0N/A * An example of an RDN is "OU=Sales+CN=J.Smith". In this example,
0N/A * the RDN consist of multiple attribute type/value pairs. The
0N/A * RDN is parsed as described in the class description for
0N/A * {@link javax.naming.ldap.LdapName <tt>LdapName</tt>}.
0N/A * <p>
0N/A * The Rdn class represents an RDN as attribute type/value mappings,
0N/A * which can be viewed using
0N/A * {@link javax.naming.directory.Attributes Attributes}.
0N/A * In addition, it contains convenience methods that allow easy retrieval
0N/A * of type and value when the Rdn consist of a single type/value pair,
0N/A * which is how it appears in a typical usage.
0N/A * It also contains helper methods that allow escaping of the unformatted
0N/A * attribute value and unescaping of the value formatted according to the
0N/A * escaping syntax defined in RFC2253. For methods that take or return
0N/A * attribute value as an Object, the value is either a String
0N/A * (in unescaped form) or a byte array.
0N/A * <p>
0N/A * <code>Rdn</code> will properly parse all valid RDNs, but
0N/A * does not attempt to detect all possible violations when parsing
0N/A * invalid RDNs. It is "generous" in accepting invalid RDNs.
0N/A * The "validity" of a name is determined ultimately when it
0N/A * is supplied to an LDAP server, which may accept or
0N/A * reject the name based on factors such as its schema information
0N/A * and interoperability considerations.
0N/A *
0N/A * <p>
0N/A * The following code example shows how to construct an Rdn using the
0N/A * constructor that takes type and value as arguments:
0N/A * <pre>
0N/A * Rdn rdn = new Rdn("cn", "Juicy, Fruit");
0N/A * System.out.println(rdn.toString());
0N/A * </pre>
0N/A * The last line will print <tt>cn=Juicy\, Fruit</tt>. The
0N/A * {@link #unescapeValue(String) <tt>unescapeValue()</tt>} method can be
0N/A * used to unescape the escaped comma resulting in the original
0N/A * value <tt>"Juicy, Fruit"</tt>. The {@link #escapeValue(Object)
0N/A * <tt>escapeValue()</tt>} method adds the escape back preceding the comma.
0N/A * <p>
0N/A * This class can be instantiated by a string representation
0N/A * of the RDN defined in RFC 2253 as shown in the following code example:
0N/A * <pre>
0N/A * Rdn rdn = new Rdn("cn=Juicy\\, Fruit");
0N/A * System.out.println(rdn.toString());
0N/A * </pre>
0N/A * The last line will print <tt>cn=Juicy\, Fruit</tt>.
0N/A * <p>
0N/A * Concurrent multithreaded read-only access of an instance of
0N/A * <tt>Rdn</tt> need not be synchronized.
0N/A * <p>
0N/A * Unless otherwise noted, the behavior of passing a null argument
0N/A * to a constructor or method in this class will cause NullPointerException
0N/A * to be thrown.
0N/A *
0N/A * @since 1.5
0N/A */
0N/A
0N/Apublic class Rdn implements Serializable, Comparable<Object> {
0N/A
0N/A // private transient ArrayList<RdnEntry> entries;
0N/A private transient ArrayList entries;
0N/A
0N/A // The common case.
0N/A private static final int DEFAULT_SIZE = 1;
0N/A
0N/A private static final long serialVersionUID = -5994465067210009656L;
0N/A
0N/A /**
0N/A * Constructs an Rdn from the given attribute set. See
0N/A * {@link javax.naming.directory.Attributes Attributes}.
0N/A * <p>
0N/A * The string attribute values are not interpretted as
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
0N/A * formatted RDN strings. That is, the values are used
0N/A * literally (not parsed) and assumed to be unescaped.
0N/A *
0N/A * @param attrSet The non-null and non-empty attributes containing
0N/A * type/value mappings.
0N/A * @throws InvalidNameException If contents of <tt>attrSet</tt> cannot
0N/A * be used to construct a valid RDN.
0N/A */
0N/A public Rdn(Attributes attrSet) throws InvalidNameException {
0N/A if (attrSet.size() == 0) {
0N/A throw new InvalidNameException("Attributes cannot be empty");
0N/A }
0N/A entries = new ArrayList(attrSet.size());
0N/A NamingEnumeration attrs = attrSet.getAll();
0N/A try {
0N/A for (int nEntries = 0; attrs.hasMore(); nEntries++) {
0N/A RdnEntry entry = new RdnEntry();
0N/A Attribute attr = (Attribute) attrs.next();
0N/A entry.type = attr.getID();
0N/A entry.value = attr.get();
0N/A entries.add(nEntries, entry);
0N/A }
0N/A } catch (NamingException e) {
0N/A InvalidNameException e2 = new InvalidNameException(
0N/A e.getMessage());
0N/A e2.initCause(e);
0N/A throw e2;
0N/A }
0N/A sort(); // arrange entries for comparison
0N/A }
0N/A
0N/A /**
0N/A * Constructs an Rdn from the given string.
0N/A * This constructor takes a string formatted according to the rules
2507N/A * defined in <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
0N/A * and described in the class description for
0N/A * {@link javax.naming.ldap.LdapName}.
0N/A *
0N/A * @param rdnString The non-null and non-empty RFC2253 formatted string.
0N/A * @throws InvalidNameException If a syntax error occurs during
0N/A * parsing of the rdnString.
0N/A */
0N/A public Rdn(String rdnString) throws InvalidNameException {
0N/A entries = new ArrayList(DEFAULT_SIZE);
0N/A (new Rfc2253Parser(rdnString)).parseRdn(this);
0N/A }
0N/A
0N/A /**
0N/A * Constructs an Rdn from the given <tt>rdn</tt>.
0N/A * The contents of the <tt>rdn</tt> are simply copied into the newly
0N/A * created Rdn.
0N/A * @param rdn The non-null Rdn to be copied.
0N/A */
0N/A public Rdn(Rdn rdn) {
0N/A entries = new ArrayList(rdn.entries.size());
0N/A entries.addAll(rdn.entries);
0N/A }
0N/A
0N/A /**
0N/A * Constructs an Rdn from the given attribute type and
0N/A * value.
0N/A * The string attribute values are not interpretted as
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
0N/A * formatted RDN strings. That is, the values are used
0N/A * literally (not parsed) and assumed to be unescaped.
0N/A *
0N/A * @param type The non-null and non-empty string attribute type.
0N/A * @param value The non-null and non-empty attribute value.
0N/A * @throws InvalidNameException If type/value cannot be used to
0N/A * construct a valid RDN.
0N/A * @see #toString()
0N/A */
0N/A public Rdn(String type, Object value) throws InvalidNameException {
0N/A if (value == null) {
0N/A throw new NullPointerException("Cannot set value to null");
0N/A }
0N/A if (type.equals("") || isEmptyValue(value)) {
0N/A throw new InvalidNameException(
0N/A "type or value cannot be empty, type:" + type +
0N/A " value:" + value);
0N/A }
0N/A entries = new ArrayList(DEFAULT_SIZE);
0N/A put(type, value);
0N/A }
0N/A
0N/A private boolean isEmptyValue(Object val) {
0N/A return ((val instanceof String) && val.equals("")) ||
0N/A ((val instanceof byte[]) && (((byte[]) val).length == 0));
0N/A }
0N/A
0N/A // An empty constructor used by the parser
0N/A Rdn() {
0N/A entries = new ArrayList(DEFAULT_SIZE);
0N/A }
0N/A
0N/A /*
0N/A * Adds the given attribute type and value to this Rdn.
0N/A * The string attribute values are not interpretted as
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>
0N/A * formatted RDN strings. That is the values are used
0N/A * literally (not parsed) and assumed to be unescaped.
0N/A *
0N/A * @param type The non-null and non-empty string attribute type.
0N/A * @param value The non-null and non-empty attribute value.
0N/A * @return The updated Rdn, not a new one. Cannot be null.
0N/A * @see #toString()
0N/A */
0N/A Rdn put(String type, Object value) {
0N/A
0N/A // create new Entry
0N/A RdnEntry newEntry = new RdnEntry();
0N/A newEntry.type = type;
0N/A if (value instanceof byte[]) { // clone the byte array
0N/A newEntry.value = ((byte[]) value).clone();
0N/A } else {
0N/A newEntry.value = value;
0N/A }
0N/A entries.add(newEntry);
0N/A return this;
0N/A }
0N/A
0N/A void sort() {
0N/A if (entries.size() > 1) {
0N/A Collections.sort(entries);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Retrieves one of this Rdn's value.
0N/A * This is a convenience method for obtaining the value,
0N/A * when the RDN contains a single type and value mapping,
0N/A * which is the common RDN usage.
0N/A * <p>
0N/A * For a multi-valued RDN, this method returns value corresponding
0N/A * to the type returned by {@link #getType() getType()} method.
0N/A *
0N/A * @return The non-null attribute value.
0N/A */
0N/A public Object getValue() {
0N/A return ((RdnEntry) entries.get(0)).getValue();
0N/A }
0N/A
0N/A /**
0N/A * Retrieves one of this Rdn's type.
0N/A * This is a convenience method for obtaining the type,
0N/A * when the RDN contains a single type and value mapping,
0N/A * which is the common RDN usage.
0N/A * <p>
0N/A * For a multi-valued RDN, the type/value pairs have
0N/A * no specific order defined on them. In that case, this method
0N/A * returns type of one of the type/value pairs.
0N/A * The {@link #getValue() getValue()} method returns the
0N/A * value corresponding to the type returned by this method.
0N/A *
0N/A * @return The non-null attribute type.
0N/A */
0N/A public String getType() {
0N/A return ((RdnEntry) entries.get(0)).getType();
0N/A }
0N/A
0N/A /**
0N/A * Returns this Rdn as a string represented in a format defined by
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> and described
0N/A * in the class description for {@link javax.naming.ldap.LdapName LdapName}.
0N/A *
0N/A * @return The string representation of the Rdn.
0N/A */
0N/A public String toString() {
0N/A StringBuilder builder = new StringBuilder();
0N/A int size = entries.size();
0N/A if (size > 0) {
0N/A builder.append(entries.get(0));
0N/A }
0N/A for (int next = 1; next < size; next++) {
0N/A builder.append('+');
0N/A builder.append(entries.get(next));
0N/A }
0N/A return builder.toString();
0N/A }
0N/A
0N/A /**
0N/A * Compares this Rdn with the specified Object for order.
0N/A * Returns a negative integer, zero, or a positive integer as this
0N/A * Rdn is less than, equal to, or greater than the given Object.
0N/A * <p>
0N/A * If obj is null or not an instance of Rdn, ClassCastException
0N/A * is thrown.
0N/A * <p>
0N/A * The attribute type and value pairs of the RDNs are lined up
0N/A * against each other and compared lexicographically. The order of
0N/A * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
0N/A * significant.
0N/A *
0N/A * @param obj The non-null object to compare against.
0N/A * @return A negative integer, zero, or a positive integer as this Rdn
0N/A * is less than, equal to, or greater than the given Object.
0N/A * @exception ClassCastException if obj is null or not a Rdn.
0N/A * <p>
0N/A */
0N/A public int compareTo(Object obj) {
0N/A if (!(obj instanceof Rdn)) {
0N/A throw new ClassCastException("The obj is not a Rdn");
0N/A }
0N/A if (obj == this) {
0N/A return 0;
0N/A }
0N/A Rdn that = (Rdn) obj;
0N/A int minSize = Math.min(entries.size(), that.entries.size());
0N/A for (int i = 0; i < minSize; i++) {
0N/A
0N/A // Compare a single pair of type/value pairs.
0N/A int diff = ((RdnEntry) entries.get(i)).compareTo(
0N/A that.entries.get(i));
0N/A if (diff != 0) {
0N/A return diff;
0N/A }
0N/A }
0N/A return (entries.size() - that.entries.size()); // longer RDN wins
0N/A }
0N/A
0N/A /**
0N/A * Compares the specified Object with this Rdn for equality.
0N/A * Returns true if the given object is also a Rdn and the two Rdns
0N/A * represent the same attribute type and value mappings. The order of
0N/A * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
0N/A * significant.
0N/A * <p>
0N/A * Type and value equalilty matching is done as below:
0N/A * <ul>
0N/A * <li> The types are compared for equality with their case ignored.
0N/A * <li> String values with different but equivalent usage of quoting,
0N/A * escaping, or UTF8-hex-encoding are considered equal.
0N/A * The case of the values is ignored during the comparison.
0N/A * </ul>
0N/A * <p>
0N/A * If obj is null or not an instance of Rdn, false is returned.
0N/A * <p>
0N/A * @param obj object to be compared for equality with this Rdn.
0N/A * @return true if the specified object is equal to this Rdn.
0N/A * @see #hashCode()
0N/A */
0N/A public boolean equals(Object obj) {
0N/A if (obj == this) {
0N/A return true;
0N/A }
0N/A if (!(obj instanceof Rdn)) {
0N/A return false;
0N/A }
0N/A Rdn that = (Rdn) obj;
0N/A if (entries.size() != that.size()) {
0N/A return false;
0N/A }
0N/A for (int i = 0; i < entries.size(); i++) {
0N/A if (!entries.get(i).equals(that.entries.get(i))) {
0N/A return false;
0N/A }
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A /**
0N/A * Returns the hash code of this RDN. Two RDNs that are
0N/A * equal (according to the equals method) will have the same
0N/A * hash code.
0N/A *
0N/A * @return An int representing the hash code of this Rdn.
0N/A * @see #equals
0N/A */
0N/A public int hashCode() {
0N/A
0N/A // Sum up the hash codes of the components.
0N/A int hash = 0;
0N/A
0N/A // For each type/value pair...
0N/A for (int i = 0; i < entries.size(); i++) {
0N/A hash += entries.get(i).hashCode();
0N/A }
0N/A return hash;
0N/A }
0N/A
0N/A /**
0N/A * Retrieves the {@link javax.naming.directory.Attributes Attributes}
0N/A * view of the type/value mappings contained in this Rdn.
0N/A *
0N/A * @return The non-null attributes containing the type/value
0N/A * mappings of this Rdn.
0N/A */
0N/A public Attributes toAttributes() {
0N/A Attributes attrs = new BasicAttributes(true);
0N/A for (int i = 0; i < entries.size(); i++) {
0N/A RdnEntry entry = (RdnEntry) entries.get(i);
0N/A Attribute attr = attrs.put(entry.getType(), entry.getValue());
0N/A if (attr != null) {
0N/A attr.add(entry.getValue());
0N/A attrs.put(attr);
0N/A }
0N/A }
0N/A return attrs;
0N/A }
0N/A
0N/A
0N/A private static class RdnEntry implements Comparable {
0N/A private String type;
0N/A private Object value;
0N/A
0N/A // If non-null, a cannonical representation of the value suitable
0N/A // for comparison using String.compareTo()
0N/A private String comparable = null;
0N/A
0N/A String getType() {
0N/A return type;
0N/A }
0N/A
0N/A Object getValue() {
0N/A return value;
0N/A }
0N/A
0N/A public int compareTo(Object obj) {
0N/A
0N/A // Any change here affecting equality must be
0N/A // reflected in hashCode().
0N/A RdnEntry that = (RdnEntry) obj;
0N/A
0N/A int diff = type.toUpperCase().compareTo(
0N/A that.type.toUpperCase());
0N/A if (diff != 0) {
0N/A return diff;
0N/A }
0N/A if (value.equals(that.value)) { // try shortcut
0N/A return 0;
0N/A }
0N/A return getValueComparable().compareTo(
0N/A that.getValueComparable());
0N/A }
0N/A
0N/A public boolean equals(Object obj) {
0N/A if (obj == this) {
0N/A return true;
0N/A }
0N/A if (!(obj instanceof RdnEntry)) {
0N/A return false;
0N/A }
0N/A
0N/A // Any change here must be reflected in hashCode()
0N/A RdnEntry that = (RdnEntry) obj;
0N/A return (type.equalsIgnoreCase(that.type)) &&
0N/A (getValueComparable().equals(
0N/A that.getValueComparable()));
0N/A }
0N/A
0N/A public int hashCode() {
0N/A return (type.toUpperCase().hashCode() +
0N/A getValueComparable().hashCode());
0N/A }
0N/A
0N/A public String toString() {
0N/A return type + "=" + escapeValue(value);
0N/A }
0N/A
0N/A private String getValueComparable() {
0N/A if (comparable != null) {
0N/A return comparable; // return cached result
0N/A }
0N/A
0N/A // cache result
0N/A if (value instanceof byte[]) {
0N/A comparable = escapeBinaryValue((byte[]) value);
0N/A } else {
0N/A comparable = ((String) value).toUpperCase();
0N/A }
0N/A return comparable;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Retrieves the number of attribute type/value pairs in this Rdn.
0N/A * @return The non-negative number of type/value pairs in this Rdn.
0N/A */
0N/A public int size() {
0N/A return entries.size();
0N/A }
0N/A
0N/A /**
0N/A * Given the value of an attribute, returns a string escaped according
0N/A * to the rules specified in
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
0N/A * <p>
0N/A * For example, if the val is "Sue, Grabbit and Runn", the escaped
0N/A * value returned by this method is "Sue\, Grabbit and Runn".
0N/A * <p>
0N/A * A string value is represented as a String and binary value
0N/A * as a byte array.
0N/A *
0N/A * @param val The non-null object to be escaped.
0N/A * @return Escaped string value.
0N/A * @throws ClassCastException if val is is not a String or byte array.
0N/A */
0N/A public static String escapeValue(Object val) {
0N/A return (val instanceof byte[])
0N/A ? escapeBinaryValue((byte[])val)
0N/A : escapeStringValue((String)val);
0N/A }
0N/A
0N/A /*
0N/A * Given the value of a string-valued attribute, returns a
0N/A * string suitable for inclusion in a DN. This is accomplished by
0N/A * using backslash (\) to escape the following characters:
0N/A * leading and trailing whitespace
0N/A * , = + < > # ; " \
0N/A */
0N/A private static final String escapees = ",=+<>#;\"\\";
0N/A
0N/A private static String escapeStringValue(String val) {
0N/A
0N/A char[] chars = val.toCharArray();
0N/A StringBuilder builder = new StringBuilder(2 * val.length());
0N/A
0N/A // Find leading and trailing whitespace.
0N/A int lead; // index of first char that is not leading whitespace
0N/A for (lead = 0; lead < chars.length; lead++) {
0N/A if (!isWhitespace(chars[lead])) {
0N/A break;
0N/A }
0N/A }
0N/A int trail; // index of last char that is not trailing whitespace
0N/A for (trail = chars.length - 1; trail >= 0; trail--) {
0N/A if (!isWhitespace(chars[trail])) {
0N/A break;
0N/A }
0N/A }
0N/A
0N/A for (int i = 0; i < chars.length; i++) {
0N/A char c = chars[i];
0N/A if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {
0N/A builder.append('\\');
0N/A }
0N/A builder.append(c);
0N/A }
0N/A return builder.toString();
0N/A }
0N/A
0N/A /*
0N/A * Given the value of a binary attribute, returns a string
0N/A * suitable for inclusion in a DN (such as "#CEB1DF80").
0N/A * TBD: This method should actually generate the ber encoding
0N/A * of the binary value
0N/A */
0N/A private static String escapeBinaryValue(byte[] val) {
0N/A
0N/A StringBuilder builder = new StringBuilder(1 + 2 * val.length);
0N/A builder.append("#");
0N/A
0N/A for (int i = 0; i < val.length; i++) {
0N/A byte b = val[i];
0N/A builder.append(Character.forDigit(0xF & (b >>> 4), 16));
0N/A builder.append(Character.forDigit(0xF & b, 16));
0N/A }
0N/A return builder.toString();
0N/A // return builder.toString().toUpperCase();
0N/A }
0N/A
0N/A /**
0N/A * Given an attribute value string formated according to the rules
0N/A * specified in
2507N/A * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>,
0N/A * returns the unformated value. Escapes and quotes are
0N/A * stripped away, and hex-encoded UTF-8 is converted to equivalent
0N/A * UTF-16 characters. Returns a string value as a String, and a
0N/A * binary value as a byte array.
0N/A * <p>
0N/A * Legal and illegal values are defined in RFC 2253.
0N/A * This method is generous in accepting the values and does not
0N/A * catch all illegal values.
0N/A * Therefore, passing in an illegal value might not necessarily
0N/A * trigger an <tt>IllegalArgumentException</tt>.
0N/A *
0N/A * @param val The non-null string to be unescaped.
0N/A * @return Unescaped value.
0N/A * @throws IllegalArgumentException When an Illegal value
0N/A * is provided.
0N/A */
0N/A public static Object unescapeValue(String val) {
0N/A
0N/A char[] chars = val.toCharArray();
0N/A int beg = 0;
0N/A int end = chars.length;
0N/A
0N/A // Trim off leading and trailing whitespace.
0N/A while ((beg < end) && isWhitespace(chars[beg])) {
0N/A ++beg;
0N/A }
0N/A
0N/A while ((beg < end) && isWhitespace(chars[end - 1])) {
0N/A --end;
0N/A }
0N/A
0N/A // Add back the trailing whitespace with a preceeding '\'
0N/A // (escaped or unescaped) that was taken off in the above
0N/A // loop. Whether or not to retain this whitespace is decided below.
0N/A if (end != chars.length &&
0N/A (beg < end) &&
0N/A chars[end - 1] == '\\') {
0N/A end++;
0N/A }
0N/A if (beg >= end) {
0N/A return "";
0N/A }
0N/A
0N/A if (chars[beg] == '#') {
0N/A // Value is binary (eg: "#CEB1DF80").
0N/A return decodeHexPairs(chars, ++beg, end);
0N/A }
0N/A
0N/A // Trim off quotes.
0N/A if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
0N/A ++beg;
0N/A --end;
0N/A }
0N/A
0N/A StringBuilder builder = new StringBuilder(end - beg);
0N/A int esc = -1; // index of the last escaped character
0N/A
0N/A for (int i = beg; i < end; i++) {
0N/A if ((chars[i] == '\\') && (i + 1 < end)) {
0N/A if (!Character.isLetterOrDigit(chars[i + 1])) {
0N/A ++i; // skip backslash
0N/A builder.append(chars[i]); // snarf escaped char
0N/A esc = i;
0N/A } else {
0N/A
0N/A // Convert hex-encoded UTF-8 to 16-bit chars.
0N/A byte[] utf8 = getUtf8Octets(chars, i, end);
0N/A if (utf8.length > 0) {
0N/A try {
0N/A builder.append(new String(utf8, "UTF8"));
0N/A } catch (java.io.UnsupportedEncodingException e) {
0N/A // shouldn't happen
0N/A }
0N/A i += utf8.length * 3 - 1;
0N/A } else { // no utf8 bytes available, invalid DN
0N/A
0N/A // '/' has no meaning, throw exception
0N/A throw new IllegalArgumentException(
0N/A "Not a valid attribute string value:" +
0N/A val + ",improper usage of backslash");
0N/A }
0N/A }
0N/A } else {
0N/A builder.append(chars[i]); // snarf unescaped char
0N/A }
0N/A }
0N/A
0N/A // Get rid of the unescaped trailing whitespace with the
0N/A // preceeding '\' character that was previously added back.
0N/A int len = builder.length();
0N/A if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) {
0N/A builder.setLength(len - 1);
0N/A }
0N/A return builder.toString();
0N/A }
0N/A
0N/A
0N/A /*
0N/A * Given an array of chars (with starting and ending indexes into it)
0N/A * representing bytes encoded as hex-pairs (such as "CEB1DF80"),
0N/A * returns a byte array containing the decoded bytes.
0N/A */
0N/A private static byte[] decodeHexPairs(char[] chars, int beg, int end) {
0N/A byte[] bytes = new byte[(end - beg) / 2];
0N/A for (int i = 0; beg + 1 < end; i++) {
0N/A int hi = Character.digit(chars[beg], 16);
0N/A int lo = Character.digit(chars[beg + 1], 16);
0N/A if (hi < 0 || lo < 0) {
0N/A break;
0N/A }
0N/A bytes[i] = (byte)((hi<<4) + lo);
0N/A beg += 2;
0N/A }
0N/A if (beg != end) {
0N/A throw new IllegalArgumentException(
0N/A "Illegal attribute value: " + new String(chars));
0N/A }
0N/A return bytes;
0N/A }
0N/A
0N/A /*
0N/A * Given an array of chars (with starting and ending indexes into it),
0N/A * finds the largest prefix consisting of hex-encoded UTF-8 octets,
0N/A * and returns a byte array containing the corresponding UTF-8 octets.
0N/A *
0N/A * Hex-encoded UTF-8 octets look like this:
0N/A * \03\B1\DF\80
0N/A */
0N/A private static byte[] getUtf8Octets(char[] chars, int beg, int end) {
0N/A byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room
0N/A int len = 0; // index of first unused byte in utf8
0N/A
0N/A while ((beg + 2 < end) &&
0N/A (chars[beg++] == '\\')) {
0N/A int hi = Character.digit(chars[beg++], 16);
0N/A int lo = Character.digit(chars[beg++], 16);
0N/A if (hi < 0 || lo < 0) {
0N/A break;
0N/A }
0N/A utf8[len++] = (byte)((hi<<4) + lo);
0N/A }
0N/A if (len == utf8.length) {
0N/A return utf8;
0N/A } else {
0N/A byte[] res = new byte[len];
0N/A System.arraycopy(utf8, 0, res, 0, len);
0N/A return res;
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * Best guess as to what RFC 2253 means by "whitespace".
0N/A */
0N/A private static boolean isWhitespace(char c) {
0N/A return (c == ' ' || c == '\r');
0N/A }
0N/A
0N/A /**
0N/A * Serializes only the unparsed RDN, for compactness and to avoid
0N/A * any implementation dependency.
0N/A *
0N/A * @serialData The RDN string
0N/A */
0N/A private void writeObject(ObjectOutputStream s)
0N/A throws java.io.IOException {
0N/A s.defaultWriteObject();
0N/A s.writeObject(toString());
0N/A }
0N/A
0N/A private void readObject(ObjectInputStream s)
0N/A throws IOException, ClassNotFoundException {
0N/A s.defaultReadObject();
0N/A entries = new ArrayList(DEFAULT_SIZE);
0N/A String unparsed = (String) s.readObject();
0N/A try {
0N/A (new Rfc2253Parser(unparsed)).parseRdn(this);
0N/A } catch (InvalidNameException e) {
0N/A // shouldn't happen
0N/A throw new java.io.StreamCorruptedException(
0N/A "Invalid name: " + unparsed);
0N/A }
0N/A }
0N/A}