/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.jndi.ldap;


import java.util.Enumeration;
import java.util.Vector;
import java.util.Locale;

import javax.naming.*;
import javax.naming.directory.Attributes;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttributes;


LdapName implements compound names for LDAP v3 as specified by RFC 2253.

RFC 2253 has a few ambiguities and outright inconsistencies. These are resolved as follows:

  • RFC 2253 leaves the term "whitespace" undefined. The definition of "optional-space" given in RFC 1779 is used in its place: either a space character or a carriage return ("\r").
  • Whitespace is allowed on either side of ',', ';', '=', and '+'. Such whitespace is accepted but not generated by this code, and is ignored when comparing names.
  • AttributeValue strings containing '=' or non-leading '#' characters (unescaped) are accepted.

String names passed to LdapName or returned by it use the full 16-bit Unicode character set. They may also contain characters encoded into UTF-8 with each octet represented by a three-character substring such as "\\B4". They may not, however, contain characters encoded into UTF-8 with each octet represented by a single character in the string: the meaning would be ambiguous.

LdapName will properly parse all valid names, but does not attempt to detect all possible violations when parsing invalid names. It's "generous".

When names are tested for equality, attribute types and binary values are case-insensitive, and string values are by default case-insensitive. String values with different but equivalent usage of quoting, escaping, or UTF8-hex-encoding are considered equal. The order of components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not significant.

Author:Scott Seligman
/** * <code>LdapName</code> implements compound names for LDAP v3 as * specified by RFC 2253. *<p> * RFC 2253 has a few ambiguities and outright inconsistencies. These * are resolved as follows: * <ul> * <li> RFC 2253 leaves the term "whitespace" undefined. The * definition of "optional-space" given in RFC 1779 is used in * its place: either a space character or a carriage return ("\r"). * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'. * Such whitespace is accepted but not generated by this code, * and is ignored when comparing names. * <li> AttributeValue strings containing '=' or non-leading '#' * characters (unescaped) are accepted. * </ul> *<p> * String names passed to <code>LdapName</code> or returned by it * use the full 16-bit Unicode character set. They may also contain * characters encoded into UTF-8 with each octet represented by a * three-character substring such as "\\B4". * They may not, however, contain characters encoded into UTF-8 with * each octet represented by a single character in the string: the * meaning would be ambiguous. *<p> * <code>LdapName</code> will properly parse all valid names, but * does not attempt to detect all possible violations when parsing * invalid names. It's "generous". *<p> * When names are tested for equality, attribute types and binary * values are case-insensitive, and string values are by default * case-insensitive. * String values with different but equivalent usage of quoting, * escaping, or UTF8-hex-encoding are considered equal. The order of * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not * significant. * * @author Scott Seligman */
public final class LdapName implements Name { private transient String unparsed; // if non-null, the DN in unparsed form private transient Vector<Rdn> rdns; // parsed name components private transient boolean valuesCaseSensitive = false;
Constructs an LDAP name from the given DN.
Params:
  • name – An LDAP DN. To JNDI, a compound name.
Throws:
/** * Constructs an LDAP name from the given DN. * * @param name An LDAP DN. To JNDI, a compound name. * * @throws InvalidNameException if a syntax violation is detected. */
public LdapName(String name) throws InvalidNameException { unparsed = name; parse(); } /* * Constructs an LDAP name given its parsed components and, optionally * (if "name" is not null), the unparsed DN. */ @SuppressWarnings("unchecked") // clone() private LdapName(String name, Vector<Rdn> rdns) { unparsed = name; this.rdns = (Vector<Rdn>)rdns.clone(); } /* * Constructs an LDAP name given its parsed components (the elements * of "rdns" in the range [beg,end)) and, optionally * (if "name" is not null), the unparsed DN. */ private LdapName(String name, Vector<Rdn> rdns, int beg, int end) { unparsed = name; this.rdns = new Vector<>(); for (int i = beg; i < end; i++) { this.rdns.addElement(rdns.elementAt(i)); } } public Object clone() { return new LdapName(unparsed, rdns); } public String toString() { if (unparsed != null) { return unparsed; } StringBuffer buf = new StringBuffer(); for (int i = rdns.size() - 1; i >= 0; i--) { if (i < rdns.size() - 1) { buf.append(','); } Rdn rdn = rdns.elementAt(i); buf.append(rdn); } unparsed = new String(buf); return unparsed; } public boolean equals(Object obj) { return ((obj instanceof LdapName) && (compareTo(obj) == 0)); } public int compareTo(Object obj) { LdapName that = (LdapName)obj; if ((obj == this) || // check possible shortcuts (unparsed != null && unparsed.equals(that.unparsed))) { return 0; } // Compare RDNs one by one, lexicographically. int minSize = Math.min(rdns.size(), that.rdns.size()); for (int i = 0 ; i < minSize; i++) { // Compare a single pair of RDNs. Rdn rdn1 = rdns.elementAt(i); Rdn rdn2 = that.rdns.elementAt(i); int diff = rdn1.compareTo(rdn2); if (diff != 0) { return diff; } } return (rdns.size() - that.rdns.size()); // longer DN wins } public int hashCode() { // Sum up the hash codes of the components. int hash = 0; // For each RDN... for (int i = 0; i < rdns.size(); i++) { Rdn rdn = rdns.elementAt(i); hash += rdn.hashCode(); } return hash; } public int size() { return rdns.size(); } public boolean isEmpty() { return rdns.isEmpty(); } public Enumeration<String> getAll() { final Enumeration<Rdn> enum_ = rdns.elements(); return new Enumeration<String>() { public boolean hasMoreElements() { return enum_.hasMoreElements(); } public String nextElement() { return enum_.nextElement().toString(); } }; } public String get(int pos) { return rdns.elementAt(pos).toString(); } public Name getPrefix(int pos) { return new LdapName(null, rdns, 0, pos); } public Name getSuffix(int pos) { return new LdapName(null, rdns, pos, rdns.size()); } public boolean startsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(0, len2, n)); } public boolean endsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(len1 - len2, len1, n)); }
Controls whether string-values are treated as case-sensitive when the string values within names are compared. The default behavior is case-insensitive comparison.
/** * Controls whether string-values are treated as case-sensitive * when the string values within names are compared. The default * behavior is case-insensitive comparison. */
public void setValuesCaseSensitive(boolean caseSensitive) { toString(); rdns = null; // clear any cached information try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new IllegalStateException("Cannot parse name: " + unparsed); } valuesCaseSensitive = caseSensitive; } /* * Helper method for startsWith() and endsWith(). * Returns true if components [beg,end) match the components of "n". * If "n" is not an LdapName, each of its components is parsed as * the string form of an RDN. * The following must hold: end - beg == n.size(). */ private boolean matches(int beg, int end, Name n) { for (int i = beg; i < end; i++) { Rdn rdn; if (n instanceof LdapName) { LdapName ln = (LdapName)n; rdn = ln.rdns.elementAt(i - beg); } else { String rdnString = n.get(i - beg); try { rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); } catch (InvalidNameException e) { return false; } } if (!rdn.equals(rdns.elementAt(i))) { return false; } } return true; } public Name addAll(Name suffix) throws InvalidNameException { return addAll(size(), suffix); } /* * If "suffix" is not an LdapName, each of its components is parsed as * the string form of an RDN. */ public Name addAll(int pos, Name suffix) throws InvalidNameException { if (suffix instanceof LdapName) { LdapName s = (LdapName)suffix; for (int i = 0; i < s.rdns.size(); i++) { rdns.insertElementAt(s.rdns.elementAt(i), pos++); } } else { Enumeration<String> comps = suffix.getAll(); while (comps.hasMoreElements()) { DnParser p = new DnParser(comps.nextElement(), valuesCaseSensitive); rdns.insertElementAt(p.getRdn(), pos++); } } unparsed = null; // no longer valid return this; } public Name add(String comp) throws InvalidNameException { return add(size(), comp); } public Name add(int pos, String comp) throws InvalidNameException { Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); rdns.insertElementAt(rdn, pos); unparsed = null; // no longer valid return this; } public Object remove(int pos) throws InvalidNameException { String comp = get(pos); rdns.removeElementAt(pos); unparsed = null; // no longer valid return comp; } private void parse() throws InvalidNameException { rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn(); } /* * Best guess as to what RFC 2253 means by "whitespace". */ private static boolean isWhitespace(char c) { return (c == ' ' || c == '\r'); }
Given the value of an attribute, returns a string suitable for inclusion in a DN. If the value is a string, this is accomplished by using backslash (\) to escape the following characters:
  • leading and trailing whitespace
  • , = + < > # ; " \
If the value is a byte array, it is converted to hex notation (such as "#CEB1DF80").
/** * Given the value of an attribute, returns a string suitable * for inclusion in a DN. If the value is a string, this is * accomplished by using backslash (\) to escape the following * characters: *<ul> *<li>leading and trailing whitespace *<li><pre>{@literal , = + < > # ; " \}</pre> *</ul> * If the value is a byte array, it is converted to hex * notation (such as "#CEB1DF80"). */
public static String escapeAttributeValue(Object val) { return TypeAndValue.escapeValue(val); }
Given an attribute value formatted according to RFC 2253, returns the unformatted value. Returns a string value as a string, and a binary value as a byte array.
/** * Given an attribute value formatted according to RFC 2253, * returns the unformatted value. Returns a string value as * a string, and a binary value as a byte array. */
public static Object unescapeAttributeValue(String val) { return TypeAndValue.unescapeValue(val); }
Serializes only the unparsed DN, for compactness and to avoid any implementation dependency.
@serialData The DN string and a boolean indicating whether the values are case sensitive.
/** * Serializes only the unparsed DN, for compactness and to avoid * any implementation dependency. * * @serialData The DN string and a boolean indicating whether * the values are case sensitive. */
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.writeObject(toString()); s.writeBoolean(valuesCaseSensitive); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { unparsed = (String)s.readObject(); valuesCaseSensitive = s.readBoolean(); try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new java.io.StreamCorruptedException( "Invalid name: " + unparsed); } } static final long serialVersionUID = -1595520034788997356L; /* * DnParser implements a recursive descent parser for a single DN. */ static class DnParser { private final String name; // DN being parsed private final char[] chars; // characters in LDAP name being parsed private final int len; // length of "chars" private int cur = 0; // index of first unconsumed char in "chars" private boolean valuesCaseSensitive; /* * Given an LDAP DN in string form, returns a parser for it. */ DnParser(String name, boolean valuesCaseSensitive) throws InvalidNameException { this.name = name; len = name.length(); chars = name.toCharArray(); this.valuesCaseSensitive = valuesCaseSensitive; } /* * Parses the DN, returning a Vector of its RDNs. */ Vector<Rdn> getDn() throws InvalidNameException { cur = 0; Vector<Rdn> rdns = new Vector<>(len / 3 + 10); // leave room for growth if (len == 0) { return rdns; } rdns.addElement(parseRdn()); while (cur < len) { if (chars[cur] == ',' || chars[cur] == ';') { ++cur; rdns.insertElementAt(parseRdn(), 0); } else { throw new InvalidNameException("Invalid name: " + name); } } return rdns; } /* * Parses the DN, if it is known to contain a single RDN. */ Rdn getRdn() throws InvalidNameException { Rdn rdn = parseRdn(); if (cur < len) { throw new InvalidNameException("Invalid RDN: " + name); } return rdn; } /* * Parses the next RDN and returns it. Throws an exception if * none is found. Leading and trailing whitespace is consumed. */ private Rdn parseRdn() throws InvalidNameException { Rdn rdn = new Rdn(); while (cur < len) { consumeWhitespace(); String attrType = parseAttrType(); consumeWhitespace(); if (cur >= len || chars[cur] != '=') { throw new InvalidNameException("Invalid name: " + name); } ++cur; // consume '=' consumeWhitespace(); String value = parseAttrValue(); consumeWhitespace(); rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive)); if (cur >= len || chars[cur] != '+') { break; } ++cur; // consume '+' } return rdn; } /* * Returns the attribute type that begins at the next unconsumed * char. No leading whitespace is expected. * This routine is more generous than RFC 2253. It accepts * attribute types composed of any nonempty combination of Unicode * letters, Unicode digits, '.', '-', and internal space characters. */ private String parseAttrType() throws InvalidNameException { final int beg = cur; while (cur < len) { char c = chars[cur]; if (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == ' ') { ++cur; } else { break; } } // Back out any trailing spaces. while ((cur > beg) && (chars[cur - 1] == ' ')) { --cur; } if (beg == cur) { throw new InvalidNameException("Invalid name: " + name); } return new String(chars, beg, cur - beg); } /* * Returns the attribute value that begins at the next unconsumed * char. No leading whitespace is expected. */ private String parseAttrValue() throws InvalidNameException { if (cur < len && chars[cur] == '#') { return parseBinaryAttrValue(); } else if (cur < len && chars[cur] == '"') { return parseQuotedAttrValue(); } else { return parseStringAttrValue(); } } private String parseBinaryAttrValue() throws InvalidNameException { final int beg = cur; ++cur; // consume '#' while (cur < len && Character.isLetterOrDigit(chars[cur])) { ++cur; } return new String(chars, beg, cur - beg); } private String parseQuotedAttrValue() throws InvalidNameException { final int beg = cur; ++cur; // consume '"' while ((cur < len) && chars[cur] != '"') { if (chars[cur] == '\\') { ++cur; // consume backslash, then what follows } ++cur; } if (cur >= len) { // no closing quote throw new InvalidNameException("Invalid name: " + name); } ++cur ; // consume closing quote return new String(chars, beg, cur - beg); } private String parseStringAttrValue() throws InvalidNameException { final int beg = cur; int esc = -1; // index of the most recently escaped character while ((cur < len) && !atTerminator()) { if (chars[cur] == '\\') { ++cur; // consume backslash, then what follows esc = cur; } ++cur; } if (cur > len) { // 'twas backslash followed by nothing throw new InvalidNameException("Invalid name: " + name); } // Trim off (unescaped) trailing whitespace. int end; for (end = cur; end > beg; end--) { if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) { break; } } return new String(chars, beg, end - beg); } private void consumeWhitespace() { while ((cur < len) && isWhitespace(chars[cur])) { ++cur; } } /* * Returns true if next unconsumed character is one that terminates * a string attribute value. */ private boolean atTerminator() { return (cur < len && (chars[cur] == ',' || chars[cur] == ';' || chars[cur] == '+')); } } /* * Class Rdn represents a set of TypeAndValue. */ static class Rdn { /* * A vector of the TypeAndValue elements of this Rdn. * It is sorted to facilitate set operations. */ private final Vector<TypeAndValue> tvs = new Vector<>(); void add(TypeAndValue tv) { // Set i to index of first element greater than tv, or to // tvs.size() if there is none. int i; for (i = 0; i < tvs.size(); i++) { int diff = tv.compareTo(tvs.elementAt(i)); if (diff == 0) { return; // tv is a duplicate: ignore it } else if (diff < 0) { break; } } tvs.insertElementAt(tv, i); } public String toString() { StringBuffer buf = new StringBuffer(); for (int i = 0; i < tvs.size(); i++) { if (i > 0) { buf.append('+'); } buf.append(tvs.elementAt(i)); } return new String(buf); } public boolean equals(Object obj) { return ((obj instanceof Rdn) && (compareTo(obj) == 0)); } // Compare TypeAndValue components one by one, lexicographically. public int compareTo(Object obj) { Rdn that = (Rdn)obj; int minSize = Math.min(tvs.size(), that.tvs.size()); for (int i = 0; i < minSize; i++) { // Compare a single pair of type/value pairs. TypeAndValue tv = tvs.elementAt(i); int diff = tv.compareTo(that.tvs.elementAt(i)); if (diff != 0) { return diff; } } return (tvs.size() - that.tvs.size()); // longer RDN wins } public int hashCode() { // Sum up the hash codes of the components. int hash = 0; // For each type/value pair... for (int i = 0; i < tvs.size(); i++) { hash += tvs.elementAt(i).hashCode(); } return hash; } Attributes toAttributes() { Attributes attrs = new BasicAttributes(true); TypeAndValue tv; Attribute attr; for (int i = 0; i < tvs.size(); i++) { tv = tvs.elementAt(i); if ((attr = attrs.get(tv.getType())) == null) { attrs.put(tv.getType(), tv.getUnescapedValue()); } else { attr.add(tv.getUnescapedValue()); } } return attrs; } } /* * Class TypeAndValue represents an attribute type and its * corresponding value. */ static class TypeAndValue { private final String type; private final String value; // value, escaped or quoted private final boolean binary; private final boolean valueCaseSensitive; // If non-null, a canonical representation of the value suitable // for comparison using String.compareTo(). private String comparable = null; TypeAndValue(String type, String value, boolean valueCaseSensitive) { this.type = type; this.value = value; binary = value.startsWith("#"); this.valueCaseSensitive = valueCaseSensitive; } public String toString() { return (type + "=" + value); } public int compareTo(Object obj) { // NB: Any change here affecting equality must be // reflected in hashCode(). TypeAndValue that = (TypeAndValue)obj; 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) { // NB: Any change here must be reflected in hashCode(). if (!(obj instanceof TypeAndValue)) { return false; } TypeAndValue that = (TypeAndValue)obj; return (type.equalsIgnoreCase(that.type) && (value.equals(that.value) || getValueComparable().equals(that.getValueComparable()))); } public int hashCode() { // If two objects are equal, their hash codes must match. return (type.toUpperCase(Locale.ENGLISH).hashCode() + getValueComparable().hashCode()); } /* * Returns the type. */ String getType() { return type; } /* * Returns the unescaped value. */ Object getUnescapedValue() { return unescapeValue(value); } /* * Returns a canonical representation of "value" suitable for * comparison using String.compareTo(). If "value" is a string, * it is returned with escapes and quotes stripped away, and * hex-encoded UTF-8 converted to 16-bit Unicode chars. * If value's case is to be ignored, it is returned in uppercase. * If "value" is binary, it is returned in uppercase but * otherwise unmodified. */ private String getValueComparable() { if (comparable != null) { return comparable; // return cached result } // cache result if (binary) { comparable = value.toUpperCase(Locale.ENGLISH); } else { comparable = (String)unescapeValue(value); if (!valueCaseSensitive) { // ignore case comparable = comparable.toUpperCase(Locale.ENGLISH); } } return comparable; } /* * Given the value of an attribute, returns a string suitable * for inclusion in a DN. */ 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 String escapeStringValue(String val) { final String escapees = ",=+<>#;\"\\"; char[] chars = val.toCharArray(); StringBuffer buf = new StringBuffer(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)) { buf.append('\\'); } buf.append(c); } return new String(buf); } /* * Given the value of a binary attribute, returns a string * suitable for inclusion in a DN (such as "#CEB1DF80"). */ private static String escapeBinaryValue(byte[] val) { StringBuffer buf = new StringBuffer(1 + 2 * val.length); buf.append("#"); for (int i = 0; i < val.length; i++) { byte b = val[i]; buf.append(Character.forDigit(0xF & (b >>> 4), 16)); buf.append(Character.forDigit(0xF & b, 16)); } return (new String(buf)).toUpperCase(Locale.ENGLISH); } /* * Given an attribute value formatted according to RFC 2253, * returns the unformatted value. Escapes and quotes are * stripped away, and hex-encoded UTF-8 is converted to 16-bit * Unicode chars. Returns a string value as a String, and a * binary value as a byte array. */ 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; } StringBuffer buf = new StringBuffer(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 buf.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 { buf.append(new String(utf8, "UTF8")); } catch (java.io.UnsupportedEncodingException e) { // shouldn't happen } i += utf8.length * 3 - 1; } else { throw new IllegalArgumentException( "Not a valid attribute string value:" + val +", improper usage of backslash"); } } } else { buf.append(chars[i]); // snarf unescaped char } } // Get rid of the unescaped trailing whitespace with the // preceding '\' character that was previously added back. int len = buf.length(); if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) { buf.setLength(len - 1); } return new String(buf); } /* * 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; } } } /* * For testing. */ /* public static void main(String[] args) { try { if (args.length == 1) { // parse and print components LdapName n = new LdapName(args[0]); Enumeration rdns = n.rdns.elements(); while (rdns.hasMoreElements()) { Rdn rdn = (Rdn)rdns.nextElement(); for (int i = 0; i < rdn.tvs.size(); i++) { System.out.print("[" + rdn.tvs.elementAt(i) + "]"); } System.out.println(); } } else { // compare two names LdapName n1 = new LdapName(args[0]); LdapName n2 = new LdapName(args[1]); n1.unparsed = null; n2.unparsed = null; boolean eq = n1.equals(n2); System.out.println("[" + n1 + (eq ? "] == [" : "] != [") + n2 + "]"); } } catch (Exception e) { e.printStackTrace(); } } */ }