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