Path: blob/master/src/java.naming/share/classes/com/sun/jndi/ldap/LdapName.java
41161 views
/*1* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.jndi.ldap;262728import java.util.Enumeration;29import java.util.Vector;30import java.util.Locale;3132import javax.naming.*;33import javax.naming.directory.Attributes;34import javax.naming.directory.Attribute;35import javax.naming.directory.BasicAttributes;363738/**39* <code>LdapName</code> implements compound names for LDAP v3 as40* specified by RFC 2253.41*<p>42* RFC 2253 has a few ambiguities and outright inconsistencies. These43* are resolved as follows:44* <ul>45* <li> RFC 2253 leaves the term "whitespace" undefined. The46* definition of "optional-space" given in RFC 1779 is used in47* its place: either a space character or a carriage return ("\r").48* <li> Whitespace is allowed on either side of ',', ';', '=', and '+'.49* Such whitespace is accepted but not generated by this code,50* and is ignored when comparing names.51* <li> AttributeValue strings containing '=' or non-leading '#'52* characters (unescaped) are accepted.53* </ul>54*<p>55* String names passed to <code>LdapName</code> or returned by it56* use the full 16-bit Unicode character set. They may also contain57* characters encoded into UTF-8 with each octet represented by a58* three-character substring such as "\\B4".59* They may not, however, contain characters encoded into UTF-8 with60* each octet represented by a single character in the string: the61* meaning would be ambiguous.62*<p>63* <code>LdapName</code> will properly parse all valid names, but64* does not attempt to detect all possible violations when parsing65* invalid names. It's "generous".66*<p>67* When names are tested for equality, attribute types and binary68* values are case-insensitive, and string values are by default69* case-insensitive.70* String values with different but equivalent usage of quoting,71* escaping, or UTF8-hex-encoding are considered equal. The order of72* components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not73* significant.74*75* @author Scott Seligman76*/7778public final class LdapName implements Name {7980private transient String unparsed; // if non-null, the DN in unparsed form81private transient Vector<Rdn> rdns; // parsed name components82private transient boolean valuesCaseSensitive = false;8384/**85* Constructs an LDAP name from the given DN.86*87* @param name An LDAP DN. To JNDI, a compound name.88*89* @throws InvalidNameException if a syntax violation is detected.90*/91public LdapName(String name) throws InvalidNameException {92unparsed = name;93parse();94}9596/*97* Constructs an LDAP name given its parsed components and, optionally98* (if "name" is not null), the unparsed DN.99*/100@SuppressWarnings("unchecked") // clone()101private LdapName(String name, Vector<Rdn> rdns) {102unparsed = name;103this.rdns = (Vector<Rdn>)rdns.clone();104}105106/*107* Constructs an LDAP name given its parsed components (the elements108* of "rdns" in the range [beg,end)) and, optionally109* (if "name" is not null), the unparsed DN.110*/111private LdapName(String name, Vector<Rdn> rdns, int beg, int end) {112unparsed = name;113this.rdns = new Vector<>();114for (int i = beg; i < end; i++) {115this.rdns.addElement(rdns.elementAt(i));116}117}118119120public Object clone() {121return new LdapName(unparsed, rdns);122}123124public String toString() {125if (unparsed != null) {126return unparsed;127}128129StringBuffer buf = new StringBuffer();130for (int i = rdns.size() - 1; i >= 0; i--) {131if (i < rdns.size() - 1) {132buf.append(',');133}134Rdn rdn = rdns.elementAt(i);135buf.append(rdn);136}137138unparsed = new String(buf);139return unparsed;140}141142public boolean equals(Object obj) {143return ((obj instanceof LdapName) &&144(compareTo(obj) == 0));145}146147public int compareTo(Object obj) {148LdapName that = (LdapName)obj;149150if ((obj == this) || // check possible shortcuts151(unparsed != null && unparsed.equals(that.unparsed))) {152return 0;153}154155// Compare RDNs one by one, lexicographically.156int minSize = Math.min(rdns.size(), that.rdns.size());157for (int i = 0 ; i < minSize; i++) {158// Compare a single pair of RDNs.159Rdn rdn1 = rdns.elementAt(i);160Rdn rdn2 = that.rdns.elementAt(i);161162int diff = rdn1.compareTo(rdn2);163if (diff != 0) {164return diff;165}166}167return (rdns.size() - that.rdns.size()); // longer DN wins168}169170public int hashCode() {171// Sum up the hash codes of the components.172int hash = 0;173174// For each RDN...175for (int i = 0; i < rdns.size(); i++) {176Rdn rdn = rdns.elementAt(i);177hash += rdn.hashCode();178}179return hash;180}181182public int size() {183return rdns.size();184}185186public boolean isEmpty() {187return rdns.isEmpty();188}189190public Enumeration<String> getAll() {191final Enumeration<Rdn> enum_ = rdns.elements();192193return new Enumeration<String>() {194public boolean hasMoreElements() {195return enum_.hasMoreElements();196}197public String nextElement() {198return enum_.nextElement().toString();199}200};201}202203public String get(int pos) {204return rdns.elementAt(pos).toString();205}206207public Name getPrefix(int pos) {208return new LdapName(null, rdns, 0, pos);209}210211public Name getSuffix(int pos) {212return new LdapName(null, rdns, pos, rdns.size());213}214215public boolean startsWith(Name n) {216int len1 = rdns.size();217int len2 = n.size();218return (len1 >= len2 &&219matches(0, len2, n));220}221222public boolean endsWith(Name n) {223int len1 = rdns.size();224int len2 = n.size();225return (len1 >= len2 &&226matches(len1 - len2, len1, n));227}228229/**230* Controls whether string-values are treated as case-sensitive231* when the string values within names are compared. The default232* behavior is case-insensitive comparison.233*/234public void setValuesCaseSensitive(boolean caseSensitive) {235toString();236rdns = null; // clear any cached information237try {238parse();239} catch (InvalidNameException e) {240// shouldn't happen241throw new IllegalStateException("Cannot parse name: " + unparsed);242}243valuesCaseSensitive = caseSensitive;244}245246/*247* Helper method for startsWith() and endsWith().248* Returns true if components [beg,end) match the components of "n".249* If "n" is not an LdapName, each of its components is parsed as250* the string form of an RDN.251* The following must hold: end - beg == n.size().252*/253private boolean matches(int beg, int end, Name n) {254for (int i = beg; i < end; i++) {255Rdn rdn;256if (n instanceof LdapName) {257LdapName ln = (LdapName)n;258rdn = ln.rdns.elementAt(i - beg);259} else {260String rdnString = n.get(i - beg);261try {262rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn();263} catch (InvalidNameException e) {264return false;265}266}267268if (!rdn.equals(rdns.elementAt(i))) {269return false;270}271}272return true;273}274275public Name addAll(Name suffix) throws InvalidNameException {276return addAll(size(), suffix);277}278279/*280* If "suffix" is not an LdapName, each of its components is parsed as281* the string form of an RDN.282*/283public Name addAll(int pos, Name suffix) throws InvalidNameException {284if (suffix instanceof LdapName) {285LdapName s = (LdapName)suffix;286for (int i = 0; i < s.rdns.size(); i++) {287rdns.insertElementAt(s.rdns.elementAt(i), pos++);288}289} else {290Enumeration<String> comps = suffix.getAll();291while (comps.hasMoreElements()) {292DnParser p = new DnParser(comps.nextElement(),293valuesCaseSensitive);294rdns.insertElementAt(p.getRdn(), pos++);295}296}297unparsed = null; // no longer valid298return this;299}300301public Name add(String comp) throws InvalidNameException {302return add(size(), comp);303}304305public Name add(int pos, String comp) throws InvalidNameException {306Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn();307rdns.insertElementAt(rdn, pos);308unparsed = null; // no longer valid309return this;310}311312public Object remove(int pos) throws InvalidNameException {313String comp = get(pos);314rdns.removeElementAt(pos);315unparsed = null; // no longer valid316return comp;317}318319320private void parse() throws InvalidNameException {321rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn();322}323324/*325* Best guess as to what RFC 2253 means by "whitespace".326*/327private static boolean isWhitespace(char c) {328return (c == ' ' || c == '\r');329}330331/**332* Given the value of an attribute, returns a string suitable333* for inclusion in a DN. If the value is a string, this is334* accomplished by using backslash (\) to escape the following335* characters:336*<ul>337*<li>leading and trailing whitespace338*<li><pre>{@literal , = + < > # ; " \}</pre>339*</ul>340* If the value is a byte array, it is converted to hex341* notation (such as "#CEB1DF80").342*/343public static String escapeAttributeValue(Object val) {344return TypeAndValue.escapeValue(val);345}346347/**348* Given an attribute value formatted according to RFC 2253,349* returns the unformatted value. Returns a string value as350* a string, and a binary value as a byte array.351*/352public static Object unescapeAttributeValue(String val) {353return TypeAndValue.unescapeValue(val);354}355356/**357* Serializes only the unparsed DN, for compactness and to avoid358* any implementation dependency.359*360* @serialData The DN string and a boolean indicating whether361* the values are case sensitive.362*/363private void writeObject(java.io.ObjectOutputStream s)364throws java.io.IOException {365s.writeObject(toString());366s.writeBoolean(valuesCaseSensitive);367}368369private void readObject(java.io.ObjectInputStream s)370throws java.io.IOException, ClassNotFoundException {371unparsed = (String)s.readObject();372valuesCaseSensitive = s.readBoolean();373try {374parse();375} catch (InvalidNameException e) {376// shouldn't happen377throw new java.io.StreamCorruptedException(378"Invalid name: " + unparsed);379}380}381382static final long serialVersionUID = -1595520034788997356L;383384385/*386* DnParser implements a recursive descent parser for a single DN.387*/388static class DnParser {389390private final String name; // DN being parsed391private final char[] chars; // characters in LDAP name being parsed392private final int len; // length of "chars"393private int cur = 0; // index of first unconsumed char in "chars"394private boolean valuesCaseSensitive;395396/*397* Given an LDAP DN in string form, returns a parser for it.398*/399DnParser(String name, boolean valuesCaseSensitive)400throws InvalidNameException {401this.name = name;402len = name.length();403chars = name.toCharArray();404this.valuesCaseSensitive = valuesCaseSensitive;405}406407/*408* Parses the DN, returning a Vector of its RDNs.409*/410Vector<Rdn> getDn() throws InvalidNameException {411cur = 0;412Vector<Rdn> rdns = new Vector<>(len / 3 + 10); // leave room for growth413414if (len == 0) {415return rdns;416}417418rdns.addElement(parseRdn());419while (cur < len) {420if (chars[cur] == ',' || chars[cur] == ';') {421++cur;422rdns.insertElementAt(parseRdn(), 0);423} else {424throw new InvalidNameException("Invalid name: " + name);425}426}427return rdns;428}429430/*431* Parses the DN, if it is known to contain a single RDN.432*/433Rdn getRdn() throws InvalidNameException {434Rdn rdn = parseRdn();435if (cur < len) {436throw new InvalidNameException("Invalid RDN: " + name);437}438return rdn;439}440441/*442* Parses the next RDN and returns it. Throws an exception if443* none is found. Leading and trailing whitespace is consumed.444*/445private Rdn parseRdn() throws InvalidNameException {446447Rdn rdn = new Rdn();448while (cur < len) {449consumeWhitespace();450String attrType = parseAttrType();451consumeWhitespace();452if (cur >= len || chars[cur] != '=') {453throw new InvalidNameException("Invalid name: " + name);454}455++cur; // consume '='456consumeWhitespace();457String value = parseAttrValue();458consumeWhitespace();459460rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive));461if (cur >= len || chars[cur] != '+') {462break;463}464++cur; // consume '+'465}466return rdn;467}468469/*470* Returns the attribute type that begins at the next unconsumed471* char. No leading whitespace is expected.472* This routine is more generous than RFC 2253. It accepts473* attribute types composed of any nonempty combination of Unicode474* letters, Unicode digits, '.', '-', and internal space characters.475*/476private String parseAttrType() throws InvalidNameException {477478final int beg = cur;479while (cur < len) {480char c = chars[cur];481if (Character.isLetterOrDigit(c) ||482c == '.' ||483c == '-' ||484c == ' ') {485++cur;486} else {487break;488}489}490// Back out any trailing spaces.491while ((cur > beg) && (chars[cur - 1] == ' ')) {492--cur;493}494495if (beg == cur) {496throw new InvalidNameException("Invalid name: " + name);497}498return new String(chars, beg, cur - beg);499}500501/*502* Returns the attribute value that begins at the next unconsumed503* char. No leading whitespace is expected.504*/505private String parseAttrValue() throws InvalidNameException {506507if (cur < len && chars[cur] == '#') {508return parseBinaryAttrValue();509} else if (cur < len && chars[cur] == '"') {510return parseQuotedAttrValue();511} else {512return parseStringAttrValue();513}514}515516private String parseBinaryAttrValue() throws InvalidNameException {517final int beg = cur;518++cur; // consume '#'519while (cur < len &&520Character.isLetterOrDigit(chars[cur])) {521++cur;522}523return new String(chars, beg, cur - beg);524}525526private String parseQuotedAttrValue() throws InvalidNameException {527528final int beg = cur;529++cur; // consume '"'530531while ((cur < len) && chars[cur] != '"') {532if (chars[cur] == '\\') {533++cur; // consume backslash, then what follows534}535++cur;536}537if (cur >= len) { // no closing quote538throw new InvalidNameException("Invalid name: " + name);539}540++cur ; // consume closing quote541542return new String(chars, beg, cur - beg);543}544545private String parseStringAttrValue() throws InvalidNameException {546547final int beg = cur;548int esc = -1; // index of the most recently escaped character549550while ((cur < len) && !atTerminator()) {551if (chars[cur] == '\\') {552++cur; // consume backslash, then what follows553esc = cur;554}555++cur;556}557if (cur > len) { // 'twas backslash followed by nothing558throw new InvalidNameException("Invalid name: " + name);559}560561// Trim off (unescaped) trailing whitespace.562int end;563for (end = cur; end > beg; end--) {564if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) {565break;566}567}568return new String(chars, beg, end - beg);569}570571private void consumeWhitespace() {572while ((cur < len) && isWhitespace(chars[cur])) {573++cur;574}575}576577/*578* Returns true if next unconsumed character is one that terminates579* a string attribute value.580*/581private boolean atTerminator() {582return (cur < len &&583(chars[cur] == ',' ||584chars[cur] == ';' ||585chars[cur] == '+'));586}587}588589590/*591* Class Rdn represents a set of TypeAndValue.592*/593static class Rdn {594595/*596* A vector of the TypeAndValue elements of this Rdn.597* It is sorted to facilitate set operations.598*/599private final Vector<TypeAndValue> tvs = new Vector<>();600601void add(TypeAndValue tv) {602603// Set i to index of first element greater than tv, or to604// tvs.size() if there is none.605int i;606for (i = 0; i < tvs.size(); i++) {607int diff = tv.compareTo(tvs.elementAt(i));608if (diff == 0) {609return; // tv is a duplicate: ignore it610} else if (diff < 0) {611break;612}613}614615tvs.insertElementAt(tv, i);616}617618public String toString() {619StringBuffer buf = new StringBuffer();620for (int i = 0; i < tvs.size(); i++) {621if (i > 0) {622buf.append('+');623}624buf.append(tvs.elementAt(i));625}626return new String(buf);627}628629public boolean equals(Object obj) {630return ((obj instanceof Rdn) &&631(compareTo(obj) == 0));632}633634// Compare TypeAndValue components one by one, lexicographically.635public int compareTo(Object obj) {636Rdn that = (Rdn)obj;637int minSize = Math.min(tvs.size(), that.tvs.size());638for (int i = 0; i < minSize; i++) {639// Compare a single pair of type/value pairs.640TypeAndValue tv = tvs.elementAt(i);641int diff = tv.compareTo(that.tvs.elementAt(i));642if (diff != 0) {643return diff;644}645}646return (tvs.size() - that.tvs.size()); // longer RDN wins647}648649public int hashCode() {650// Sum up the hash codes of the components.651int hash = 0;652653// For each type/value pair...654for (int i = 0; i < tvs.size(); i++) {655hash += tvs.elementAt(i).hashCode();656}657return hash;658}659660Attributes toAttributes() {661Attributes attrs = new BasicAttributes(true);662TypeAndValue tv;663Attribute attr;664665for (int i = 0; i < tvs.size(); i++) {666tv = tvs.elementAt(i);667if ((attr = attrs.get(tv.getType())) == null) {668attrs.put(tv.getType(), tv.getUnescapedValue());669} else {670attr.add(tv.getUnescapedValue());671}672}673return attrs;674}675}676677678/*679* Class TypeAndValue represents an attribute type and its680* corresponding value.681*/682static class TypeAndValue {683684private final String type;685private final String value; // value, escaped or quoted686private final boolean binary;687private final boolean valueCaseSensitive;688689// If non-null, a canonical representation of the value suitable690// for comparison using String.compareTo().691private String comparable = null;692693TypeAndValue(String type, String value, boolean valueCaseSensitive) {694this.type = type;695this.value = value;696binary = value.startsWith("#");697this.valueCaseSensitive = valueCaseSensitive;698}699700public String toString() {701return (type + "=" + value);702}703704public int compareTo(Object obj) {705// NB: Any change here affecting equality must be706// reflected in hashCode().707708TypeAndValue that = (TypeAndValue)obj;709710int diff = type.compareToIgnoreCase(that.type);711if (diff != 0) {712return diff;713}714if (value.equals(that.value)) { // try shortcut715return 0;716}717return getValueComparable().compareTo(that.getValueComparable());718}719720public boolean equals(Object obj) {721// NB: Any change here must be reflected in hashCode().722if (!(obj instanceof TypeAndValue)) {723return false;724}725TypeAndValue that = (TypeAndValue)obj;726return (type.equalsIgnoreCase(that.type) &&727(value.equals(that.value) ||728getValueComparable().equals(that.getValueComparable())));729}730731public int hashCode() {732// If two objects are equal, their hash codes must match.733return (type.toUpperCase(Locale.ENGLISH).hashCode() +734getValueComparable().hashCode());735}736737/*738* Returns the type.739*/740String getType() {741return type;742}743744/*745* Returns the unescaped value.746*/747Object getUnescapedValue() {748return unescapeValue(value);749}750751/*752* Returns a canonical representation of "value" suitable for753* comparison using String.compareTo(). If "value" is a string,754* it is returned with escapes and quotes stripped away, and755* hex-encoded UTF-8 converted to 16-bit Unicode chars.756* If value's case is to be ignored, it is returned in uppercase.757* If "value" is binary, it is returned in uppercase but758* otherwise unmodified.759*/760private String getValueComparable() {761if (comparable != null) {762return comparable; // return cached result763}764765// cache result766if (binary) {767comparable = value.toUpperCase(Locale.ENGLISH);768} else {769comparable = (String)unescapeValue(value);770if (!valueCaseSensitive) {771// ignore case772comparable = comparable.toUpperCase(Locale.ENGLISH);773}774}775return comparable;776}777778/*779* Given the value of an attribute, returns a string suitable780* for inclusion in a DN.781*/782static String escapeValue(Object val) {783return (val instanceof byte[])784? escapeBinaryValue((byte[])val)785: escapeStringValue((String)val);786}787788/*789* Given the value of a string-valued attribute, returns a790* string suitable for inclusion in a DN. This is accomplished by791* using backslash (\) to escape the following characters:792* leading and trailing whitespace793* , = + < > # ; " \794*/795private static String escapeStringValue(String val) {796797final String escapees = ",=+<>#;\"\\";798char[] chars = val.toCharArray();799StringBuffer buf = new StringBuffer(2 * val.length());800801// Find leading and trailing whitespace.802int lead; // index of first char that is not leading whitespace803for (lead = 0; lead < chars.length; lead++) {804if (!isWhitespace(chars[lead])) {805break;806}807}808int trail; // index of last char that is not trailing whitespace809for (trail = chars.length - 1; trail >= 0; trail--) {810if (!isWhitespace(chars[trail])) {811break;812}813}814815for (int i = 0; i < chars.length; i++) {816char c = chars[i];817if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {818buf.append('\\');819}820buf.append(c);821}822return new String(buf);823}824825/*826* Given the value of a binary attribute, returns a string827* suitable for inclusion in a DN (such as "#CEB1DF80").828*/829private static String escapeBinaryValue(byte[] val) {830831StringBuffer buf = new StringBuffer(1 + 2 * val.length);832buf.append("#");833834for (int i = 0; i < val.length; i++) {835byte b = val[i];836buf.append(Character.forDigit(0xF & (b >>> 4), 16));837buf.append(Character.forDigit(0xF & b, 16));838}839840return (new String(buf)).toUpperCase(Locale.ENGLISH);841}842843/*844* Given an attribute value formatted according to RFC 2253,845* returns the unformatted value. Escapes and quotes are846* stripped away, and hex-encoded UTF-8 is converted to 16-bit847* Unicode chars. Returns a string value as a String, and a848* binary value as a byte array.849*/850static Object unescapeValue(String val) {851852char[] chars = val.toCharArray();853int beg = 0;854int end = chars.length;855856// Trim off leading and trailing whitespace.857while ((beg < end) && isWhitespace(chars[beg])) {858++beg;859}860while ((beg < end) && isWhitespace(chars[end - 1])) {861--end;862}863864// Add back the trailing whitespace with a preceding '\'865// (escaped or unescaped) that was taken off in the above866// loop. Whether or not to retain this whitespace is867// decided below.868if (end != chars.length &&869(beg < end) &&870chars[end - 1] == '\\') {871end++;872}873if (beg >= end) {874return "";875}876877if (chars[beg] == '#') {878// Value is binary (eg: "#CEB1DF80").879return decodeHexPairs(chars, ++beg, end);880}881882// Trim off quotes.883if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {884++beg;885--end;886}887888StringBuffer buf = new StringBuffer(end - beg);889int esc = -1; // index of the last escaped character890891for (int i = beg; i < end; i++) {892if ((chars[i] == '\\') && (i + 1 < end)) {893if (!Character.isLetterOrDigit(chars[i + 1])) {894++i; // skip backslash895buf.append(chars[i]); // snarf escaped char896esc = i;897} else {898899// Convert hex-encoded UTF-8 to 16-bit chars.900byte[] utf8 = getUtf8Octets(chars, i, end);901if (utf8.length > 0) {902try {903buf.append(new String(utf8, "UTF8"));904} catch (java.io.UnsupportedEncodingException e) {905// shouldn't happen906}907i += utf8.length * 3 - 1;908} else {909throw new IllegalArgumentException(910"Not a valid attribute string value:" +911val +", improper usage of backslash");912}913}914} else {915buf.append(chars[i]); // snarf unescaped char916}917}918919// Get rid of the unescaped trailing whitespace with the920// preceding '\' character that was previously added back.921int len = buf.length();922if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) {923buf.setLength(len - 1);924}925926return new String(buf);927}928929930/*931* Given an array of chars (with starting and ending indexes into it)932* representing bytes encoded as hex-pairs (such as "CEB1DF80"),933* returns a byte array containing the decoded bytes.934*/935private static byte[] decodeHexPairs(char[] chars, int beg, int end) {936byte[] bytes = new byte[(end - beg) / 2];937for (int i = 0; beg + 1 < end; i++) {938int hi = Character.digit(chars[beg], 16);939int lo = Character.digit(chars[beg + 1], 16);940if (hi < 0 || lo < 0) {941break;942}943bytes[i] = (byte)((hi<<4) + lo);944beg += 2;945}946if (beg != end) {947throw new IllegalArgumentException(948"Illegal attribute value: #" + new String(chars));949}950return bytes;951}952953/*954* Given an array of chars (with starting and ending indexes into it),955* finds the largest prefix consisting of hex-encoded UTF-8 octets,956* and returns a byte array containing the corresponding UTF-8 octets.957*958* Hex-encoded UTF-8 octets look like this:959* \03\B1\DF\80960*/961private static byte[] getUtf8Octets(char[] chars, int beg, int end) {962byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room963int len = 0; // index of first unused byte in utf8964965while ((beg + 2 < end) &&966(chars[beg++] == '\\')) {967int hi = Character.digit(chars[beg++], 16);968int lo = Character.digit(chars[beg++], 16);969if (hi < 0 || lo < 0) {970break;971}972utf8[len++] = (byte)((hi<<4) + lo);973}974975if (len == utf8.length) {976return utf8;977} else {978byte[] res = new byte[len];979System.arraycopy(utf8, 0, res, 0, len);980return res;981}982}983}984985986/*987* For testing.988*/989/*990public static void main(String[] args) {991992try {993if (args.length == 1) { // parse and print components994LdapName n = new LdapName(args[0]);995996Enumeration rdns = n.rdns.elements();997while (rdns.hasMoreElements()) {998Rdn rdn = (Rdn)rdns.nextElement();999for (int i = 0; i < rdn.tvs.size(); i++) {1000System.out.print("[" + rdn.tvs.elementAt(i) + "]");1001}1002System.out.println();1003}10041005} else { // compare two names1006LdapName n1 = new LdapName(args[0]);1007LdapName n2 = new LdapName(args[1]);1008n1.unparsed = null;1009n2.unparsed = null;1010boolean eq = n1.equals(n2);1011System.out.println("[" + n1 + (eq ? "] == [" : "] != [")1012+ n2 + "]");1013}1014} catch (Exception e) {1015e.printStackTrace();1016}1017}1018*/1019}102010211022