0001: /*
0002: * Copyright 1999-2004 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.jndi.ldap;
0027:
0028: import java.util.Enumeration;
0029: import java.util.Vector;
0030:
0031: import javax.naming.*;
0032: import javax.naming.directory.Attributes;
0033: import javax.naming.directory.Attribute;
0034: import javax.naming.directory.BasicAttributes;
0035:
0036: /**
0037: * <code>LdapName</code> implements compound names for LDAP v3 as
0038: * specified by RFC 2253.
0039: *<p>
0040: * RFC 2253 has a few ambiguities and outright inconsistencies. These
0041: * are resolved as follows:
0042: * <ul>
0043: * <li> RFC 2253 leaves the term "whitespace" undefined. The
0044: * definition of "optional-space" given in RFC 1779 is used in
0045: * its place: either a space character or a carriage return ("\r").
0046: * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'.
0047: * Such whitespace is accepted but not generated by this code,
0048: * and is ignored when comparing names.
0049: * <li> AttributeValue strings containing '=' or non-leading '#'
0050: * characters (unescaped) are accepted.
0051: * </ul>
0052: *<p>
0053: * String names passed to <code>LdapName</code> or returned by it
0054: * use the full 16-bit Unicode character set. They may also contain
0055: * characters encoded into UTF-8 with each octet represented by a
0056: * three-character substring such as "\\B4".
0057: * They may not, however, contain characters encoded into UTF-8 with
0058: * each octet represented by a single character in the string: the
0059: * meaning would be ambiguous.
0060: *<p>
0061: * <code>LdapName</code> will properly parse all valid names, but
0062: * does not attempt to detect all possible violations when parsing
0063: * invalid names. It's "generous".
0064: *<p>
0065: * When names are tested for equality, attribute types and binary
0066: * values are case-insensitive, and string values are by default
0067: * case-insensitive.
0068: * String values with different but equivalent usage of quoting,
0069: * escaping, or UTF8-hex-encoding are considered equal. The order of
0070: * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not
0071: * significant.
0072: *
0073: * @author Scott Seligman
0074: */
0075:
0076: public final class LdapName implements Name {
0077:
0078: private transient String unparsed; // if non-null, the DN in unparsed form
0079: private transient Vector rdns; // parsed name components
0080: private transient boolean valuesCaseSensitive = false;
0081:
0082: /**
0083: * Constructs an LDAP name from the given DN.
0084: *
0085: * @param name An LDAP DN. To JNDI, a compound name.
0086: *
0087: * @throws InvalidNameException if a syntax violation is detected.
0088: */
0089: public LdapName(String name) throws InvalidNameException {
0090: unparsed = name;
0091: parse();
0092: }
0093:
0094: /*
0095: * Constructs an LDAP name given its parsed components and, optionally
0096: * (if "name" is not null), the unparsed DN.
0097: */
0098: private LdapName(String name, Vector rdns) {
0099: unparsed = name;
0100: this .rdns = (Vector) rdns.clone();
0101: }
0102:
0103: /*
0104: * Constructs an LDAP name given its parsed components (the elements
0105: * of "rdns" in the range [beg,end)) and, optionally
0106: * (if "name" is not null), the unparsed DN.
0107: */
0108: private LdapName(String name, Vector rdns, int beg, int end) {
0109: unparsed = name;
0110: this .rdns = new Vector();
0111: for (int i = beg; i < end; i++) {
0112: this .rdns.addElement(rdns.elementAt(i));
0113: }
0114: }
0115:
0116: public Object clone() {
0117: return new LdapName(unparsed, rdns);
0118: }
0119:
0120: public String toString() {
0121: if (unparsed != null) {
0122: return unparsed;
0123: }
0124:
0125: StringBuffer buf = new StringBuffer();
0126: for (int i = rdns.size() - 1; i >= 0; i--) {
0127: if (i < rdns.size() - 1) {
0128: buf.append(',');
0129: }
0130: Rdn rdn = (Rdn) rdns.elementAt(i);
0131: buf.append(rdn);
0132: }
0133:
0134: unparsed = new String(buf);
0135: return unparsed;
0136: }
0137:
0138: public boolean equals(Object obj) {
0139: return ((obj instanceof LdapName) && (compareTo(obj) == 0));
0140: }
0141:
0142: public int compareTo(Object obj) {
0143: LdapName that = (LdapName) obj;
0144:
0145: if ((obj == this ) || // check possible shortcuts
0146: (unparsed != null && unparsed.equals(that.unparsed))) {
0147: return 0;
0148: }
0149:
0150: // Compare RDNs one by one, lexicographically.
0151: int minSize = Math.min(rdns.size(), that.rdns.size());
0152: for (int i = 0; i < minSize; i++) {
0153: // Compare a single pair of RDNs.
0154: Rdn rdn1 = (Rdn) rdns.elementAt(i);
0155: Rdn rdn2 = (Rdn) that.rdns.elementAt(i);
0156:
0157: int diff = rdn1.compareTo(rdn2);
0158: if (diff != 0) {
0159: return diff;
0160: }
0161: }
0162: return (rdns.size() - that.rdns.size()); // longer DN wins
0163: }
0164:
0165: public int hashCode() {
0166: // Sum up the hash codes of the components.
0167: int hash = 0;
0168:
0169: // For each RDN...
0170: for (int i = 0; i < rdns.size(); i++) {
0171: Rdn rdn = (Rdn) rdns.elementAt(i);
0172: hash += rdn.hashCode();
0173: }
0174: return hash;
0175: }
0176:
0177: public int size() {
0178: return rdns.size();
0179: }
0180:
0181: public boolean isEmpty() {
0182: return rdns.isEmpty();
0183: }
0184:
0185: public Enumeration getAll() {
0186: final Enumeration enum_ = rdns.elements();
0187:
0188: return new Enumeration() {
0189: public boolean hasMoreElements() {
0190: return enum_.hasMoreElements();
0191: }
0192:
0193: public Object nextElement() {
0194: return enum_.nextElement().toString();
0195: }
0196: };
0197: }
0198:
0199: public String get(int pos) {
0200: return rdns.elementAt(pos).toString();
0201: }
0202:
0203: public Name getPrefix(int pos) {
0204: return new LdapName(null, rdns, 0, pos);
0205: }
0206:
0207: public Name getSuffix(int pos) {
0208: return new LdapName(null, rdns, pos, rdns.size());
0209: }
0210:
0211: public boolean startsWith(Name n) {
0212: int len1 = rdns.size();
0213: int len2 = n.size();
0214: return (len1 >= len2 && matches(0, len2, n));
0215: }
0216:
0217: public boolean endsWith(Name n) {
0218: int len1 = rdns.size();
0219: int len2 = n.size();
0220: return (len1 >= len2 && matches(len1 - len2, len1, n));
0221: }
0222:
0223: /**
0224: * Controls whether string-values are treated as case-sensitive
0225: * when the string values within names are compared. The default
0226: * behavior is case-insensitive comparison.
0227: */
0228: public void setValuesCaseSensitive(boolean caseSensitive) {
0229: toString();
0230: rdns = null; // clear any cached information
0231: try {
0232: parse();
0233: } catch (InvalidNameException e) {
0234: // shouldn't happen
0235: throw new IllegalStateException("Cannot parse name: "
0236: + unparsed);
0237: }
0238: valuesCaseSensitive = caseSensitive;
0239: }
0240:
0241: /*
0242: * Helper method for startsWith() and endsWith().
0243: * Returns true if components [beg,end) match the components of "n".
0244: * If "n" is not an LdapName, each of its components is parsed as
0245: * the string form of an RDN.
0246: * The following must hold: end - beg == n.size().
0247: */
0248: private boolean matches(int beg, int end, Name n) {
0249: for (int i = beg; i < end; i++) {
0250: Rdn rdn;
0251: if (n instanceof LdapName) {
0252: LdapName ln = (LdapName) n;
0253: rdn = (Rdn) ln.rdns.elementAt(i - beg);
0254: } else {
0255: String rdnString = n.get(i - beg);
0256: try {
0257: rdn = (new DnParser(rdnString, valuesCaseSensitive))
0258: .getRdn();
0259: } catch (InvalidNameException e) {
0260: return false;
0261: }
0262: }
0263:
0264: if (!rdn.equals(rdns.elementAt(i))) {
0265: return false;
0266: }
0267: }
0268: return true;
0269: }
0270:
0271: public Name addAll(Name suffix) throws InvalidNameException {
0272: return addAll(size(), suffix);
0273: }
0274:
0275: /*
0276: * If "suffix" is not an LdapName, each of its components is parsed as
0277: * the string form of an RDN.
0278: */
0279: public Name addAll(int pos, Name suffix)
0280: throws InvalidNameException {
0281: if (suffix instanceof LdapName) {
0282: LdapName s = (LdapName) suffix;
0283: for (int i = 0; i < s.rdns.size(); i++) {
0284: rdns.insertElementAt(s.rdns.elementAt(i), pos++);
0285: }
0286: } else {
0287: Enumeration comps = suffix.getAll();
0288: while (comps.hasMoreElements()) {
0289: DnParser p = new DnParser((String) comps.nextElement(),
0290: valuesCaseSensitive);
0291: rdns.insertElementAt(p.getRdn(), pos++);
0292: }
0293: }
0294: unparsed = null; // no longer valid
0295: return this ;
0296: }
0297:
0298: public Name add(String comp) throws InvalidNameException {
0299: return add(size(), comp);
0300: }
0301:
0302: public Name add(int pos, String comp) throws InvalidNameException {
0303: Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn();
0304: rdns.insertElementAt(rdn, pos);
0305: unparsed = null; // no longer valid
0306: return this ;
0307: }
0308:
0309: public Object remove(int pos) throws InvalidNameException {
0310: String comp = get(pos);
0311: rdns.removeElementAt(pos);
0312: unparsed = null; // no longer valid
0313: return comp;
0314: }
0315:
0316: private void parse() throws InvalidNameException {
0317: rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn();
0318: }
0319:
0320: /*
0321: * Best guess as to what RFC 2253 means by "whitespace".
0322: */
0323: private static boolean isWhitespace(char c) {
0324: return (c == ' ' || c == '\r');
0325: }
0326:
0327: /**
0328: * Given the value of an attribute, returns a string suitable
0329: * for inclusion in a DN. If the value is a string, this is
0330: * accomplished by using backslash (\) to escape the following
0331: * characters:
0332: *<ul>
0333: *<li>leading and trailing whitespace
0334: *<li><pre>, = + < > # ; " \</pre>
0335: *</ul>
0336: * If the value is a byte array, it is converted to hex
0337: * notation (such as "#CEB1DF80").
0338: */
0339: public static String escapeAttributeValue(Object val) {
0340: return TypeAndValue.escapeValue(val);
0341: }
0342:
0343: /**
0344: * Given an attribute value formated according to RFC 2253,
0345: * returns the unformated value. Returns a string value as
0346: * a string, and a binary value as a byte array.
0347: */
0348: public static Object unescapeAttributeValue(String val) {
0349: return TypeAndValue.unescapeValue(val);
0350: }
0351:
0352: /**
0353: * Serializes only the unparsed DN, for compactness and to avoid
0354: * any implementation dependency.
0355: *
0356: * @serialdata The DN string and a boolean indicating whether
0357: * the values are case sensitive.
0358: */
0359: private void writeObject(java.io.ObjectOutputStream s)
0360: throws java.io.IOException {
0361: s.writeObject(toString());
0362: s.writeBoolean(valuesCaseSensitive);
0363: }
0364:
0365: private void readObject(java.io.ObjectInputStream s)
0366: throws java.io.IOException, ClassNotFoundException {
0367: unparsed = (String) s.readObject();
0368: valuesCaseSensitive = s.readBoolean();
0369: try {
0370: parse();
0371: } catch (InvalidNameException e) {
0372: // shouldn't happen
0373: throw new java.io.StreamCorruptedException("Invalid name: "
0374: + unparsed);
0375: }
0376: }
0377:
0378: static final long serialVersionUID = -1595520034788997356L;
0379:
0380: /*
0381: * DnParser implements a recursive descent parser for a single DN.
0382: */
0383: static class DnParser {
0384:
0385: private final String name; // DN being parsed
0386: private final char[] chars; // characters in LDAP name being parsed
0387: private final int len; // length of "chars"
0388: private int cur = 0; // index of first unconsumed char in "chars"
0389: private boolean valuesCaseSensitive;
0390:
0391: /*
0392: * Given an LDAP DN in string form, returns a parser for it.
0393: */
0394: DnParser(String name, boolean valuesCaseSensitive)
0395: throws InvalidNameException {
0396: this .name = name;
0397: len = name.length();
0398: chars = name.toCharArray();
0399: this .valuesCaseSensitive = valuesCaseSensitive;
0400: }
0401:
0402: /*
0403: * Parses the DN, returning a Vector of its RDNs.
0404: */
0405: Vector getDn() throws InvalidNameException {
0406: cur = 0;
0407: Vector rdns = new Vector(len / 3 + 10); // leave room for growth
0408:
0409: if (len == 0) {
0410: return rdns;
0411: }
0412:
0413: rdns.addElement(parseRdn());
0414: while (cur < len) {
0415: if (chars[cur] == ',' || chars[cur] == ';') {
0416: ++cur;
0417: rdns.insertElementAt(parseRdn(), 0);
0418: } else {
0419: throw new InvalidNameException("Invalid name: "
0420: + name);
0421: }
0422: }
0423: return rdns;
0424: }
0425:
0426: /*
0427: * Parses the DN, if it is known to contain a single RDN.
0428: */
0429: Rdn getRdn() throws InvalidNameException {
0430: Rdn rdn = parseRdn();
0431: if (cur < len) {
0432: throw new InvalidNameException("Invalid RDN: " + name);
0433: }
0434: return rdn;
0435: }
0436:
0437: /*
0438: * Parses the next RDN and returns it. Throws an exception if
0439: * none is found. Leading and trailing whitespace is consumed.
0440: */
0441: private Rdn parseRdn() throws InvalidNameException {
0442:
0443: Rdn rdn = new Rdn();
0444: while (cur < len) {
0445: consumeWhitespace();
0446: String attrType = parseAttrType();
0447: consumeWhitespace();
0448: if (cur >= len || chars[cur] != '=') {
0449: throw new InvalidNameException("Invalid name: "
0450: + name);
0451: }
0452: ++cur; // consume '='
0453: consumeWhitespace();
0454: String value = parseAttrValue();
0455: consumeWhitespace();
0456:
0457: rdn.add(new TypeAndValue(attrType, value,
0458: valuesCaseSensitive));
0459: if (cur >= len || chars[cur] != '+') {
0460: break;
0461: }
0462: ++cur; // consume '+'
0463: }
0464: return rdn;
0465: }
0466:
0467: /*
0468: * Returns the attribute type that begins at the next unconsumed
0469: * char. No leading whitespace is expected.
0470: * This routine is more generous than RFC 2253. It accepts
0471: * attribute types composed of any nonempty combination of Unicode
0472: * letters, Unicode digits, '.', '-', and internal space characters.
0473: */
0474: private String parseAttrType() throws InvalidNameException {
0475:
0476: final int beg = cur;
0477: while (cur < len) {
0478: char c = chars[cur];
0479: if (Character.isLetterOrDigit(c) || c == '.'
0480: || c == '-' || c == ' ') {
0481: ++cur;
0482: } else {
0483: break;
0484: }
0485: }
0486: // Back out any trailing spaces.
0487: while ((cur > beg) && (chars[cur - 1] == ' ')) {
0488: --cur;
0489: }
0490:
0491: if (beg == cur) {
0492: throw new InvalidNameException("Invalid name: " + name);
0493: }
0494: return new String(chars, beg, cur - beg);
0495: }
0496:
0497: /*
0498: * Returns the attribute value that begins at the next unconsumed
0499: * char. No leading whitespace is expected.
0500: */
0501: private String parseAttrValue() throws InvalidNameException {
0502:
0503: if (cur < len && chars[cur] == '#') {
0504: return parseBinaryAttrValue();
0505: } else if (cur < len && chars[cur] == '"') {
0506: return parseQuotedAttrValue();
0507: } else {
0508: return parseStringAttrValue();
0509: }
0510: }
0511:
0512: private String parseBinaryAttrValue()
0513: throws InvalidNameException {
0514: final int beg = cur;
0515: ++cur; // consume '#'
0516: while (cur < len && Character.isLetterOrDigit(chars[cur])) {
0517: ++cur;
0518: }
0519: return new String(chars, beg, cur - beg);
0520: }
0521:
0522: private String parseQuotedAttrValue()
0523: throws InvalidNameException {
0524:
0525: final int beg = cur;
0526: ++cur; // consume '"'
0527:
0528: while ((cur < len) && chars[cur] != '"') {
0529: if (chars[cur] == '\\') {
0530: ++cur; // consume backslash, then what follows
0531: }
0532: ++cur;
0533: }
0534: if (cur >= len) { // no closing quote
0535: throw new InvalidNameException("Invalid name: " + name);
0536: }
0537: ++cur; // consume closing quote
0538:
0539: return new String(chars, beg, cur - beg);
0540: }
0541:
0542: private String parseStringAttrValue()
0543: throws InvalidNameException {
0544:
0545: final int beg = cur;
0546: int esc = -1; // index of the most recently escaped character
0547:
0548: while ((cur < len) && !atTerminator()) {
0549: if (chars[cur] == '\\') {
0550: ++cur; // consume backslash, then what follows
0551: esc = cur;
0552: }
0553: ++cur;
0554: }
0555: if (cur > len) { // 'twas backslash followed by nothing
0556: throw new InvalidNameException("Invalid name: " + name);
0557: }
0558:
0559: // Trim off (unescaped) trailing whitespace.
0560: int end;
0561: for (end = cur; end > beg; end--) {
0562: if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) {
0563: break;
0564: }
0565: }
0566: return new String(chars, beg, end - beg);
0567: }
0568:
0569: private void consumeWhitespace() {
0570: while ((cur < len) && isWhitespace(chars[cur])) {
0571: ++cur;
0572: }
0573: }
0574:
0575: /*
0576: * Returns true if next unconsumed character is one that terminates
0577: * a string attribute value.
0578: */
0579: private boolean atTerminator() {
0580: return (cur < len && (chars[cur] == ','
0581: || chars[cur] == ';' || chars[cur] == '+'));
0582: }
0583: }
0584:
0585: /*
0586: * Class Rdn represents a set of TypeAndValue.
0587: */
0588: static class Rdn {
0589:
0590: /*
0591: * A vector of the TypeAndValue elements of this Rdn.
0592: * It is sorted to facilitate set operations.
0593: */
0594: private final Vector tvs = new Vector();
0595:
0596: void add(TypeAndValue tv) {
0597:
0598: // Set i to index of first element greater than tv, or to
0599: // tvs.size() if there is none.
0600: int i;
0601: for (i = 0; i < tvs.size(); i++) {
0602: int diff = tv.compareTo(tvs.elementAt(i));
0603: if (diff == 0) {
0604: return; // tv is a duplicate: ignore it
0605: } else if (diff < 0) {
0606: break;
0607: }
0608: }
0609:
0610: tvs.insertElementAt(tv, i);
0611: }
0612:
0613: public String toString() {
0614: StringBuffer buf = new StringBuffer();
0615: for (int i = 0; i < tvs.size(); i++) {
0616: if (i > 0) {
0617: buf.append('+');
0618: }
0619: buf.append(tvs.elementAt(i));
0620: }
0621: return new String(buf);
0622: }
0623:
0624: public boolean equals(Object obj) {
0625: return ((obj instanceof Rdn) && (compareTo(obj) == 0));
0626: }
0627:
0628: // Compare TypeAndValue components one by one, lexicographically.
0629: public int compareTo(Object obj) {
0630: Rdn that = (Rdn) obj;
0631: int minSize = Math.min(tvs.size(), that.tvs.size());
0632: for (int i = 0; i < minSize; i++) {
0633: // Compare a single pair of type/value pairs.
0634: TypeAndValue tv = (TypeAndValue) tvs.elementAt(i);
0635: int diff = tv.compareTo(that.tvs.elementAt(i));
0636: if (diff != 0) {
0637: return diff;
0638: }
0639: }
0640: return (tvs.size() - that.tvs.size()); // longer RDN wins
0641: }
0642:
0643: public int hashCode() {
0644: // Sum up the hash codes of the components.
0645: int hash = 0;
0646:
0647: // For each type/value pair...
0648: for (int i = 0; i < tvs.size(); i++) {
0649: hash += tvs.elementAt(i).hashCode();
0650: }
0651: return hash;
0652: }
0653:
0654: Attributes toAttributes() {
0655: Attributes attrs = new BasicAttributes(true);
0656: TypeAndValue tv;
0657: Attribute attr;
0658:
0659: for (int i = 0; i < tvs.size(); i++) {
0660: tv = (TypeAndValue) tvs.elementAt(i);
0661: if ((attr = attrs.get(tv.getType())) == null) {
0662: attrs.put(tv.getType(), tv.getUnescapedValue());
0663: } else {
0664: attr.add(tv.getUnescapedValue());
0665: }
0666: }
0667: return attrs;
0668: }
0669: }
0670:
0671: /*
0672: * Class TypeAndValue represents an attribute type and its
0673: * corresponding value.
0674: */
0675: static class TypeAndValue {
0676:
0677: private final String type;
0678: private final String value; // value, escaped or quoted
0679: private final boolean binary;
0680: private final boolean valueCaseSensitive;
0681:
0682: // If non-null, a canonical represention of the value suitable
0683: // for comparison using String.compareTo().
0684: private String comparable = null;
0685:
0686: TypeAndValue(String type, String value,
0687: boolean valueCaseSensitive) {
0688: this .type = type;
0689: this .value = value;
0690: binary = value.startsWith("#");
0691: this .valueCaseSensitive = valueCaseSensitive;
0692: }
0693:
0694: public String toString() {
0695: return (type + "=" + value);
0696: }
0697:
0698: public int compareTo(Object obj) {
0699: // NB: Any change here affecting equality must be
0700: // reflected in hashCode().
0701:
0702: TypeAndValue that = (TypeAndValue) obj;
0703:
0704: int diff = type.toUpperCase().compareTo(
0705: that.type.toUpperCase());
0706: if (diff != 0) {
0707: return diff;
0708: }
0709: if (value.equals(that.value)) { // try shortcut
0710: return 0;
0711: }
0712: return getValueComparable().compareTo(
0713: that.getValueComparable());
0714: }
0715:
0716: public boolean equals(Object obj) {
0717: // NB: Any change here must be reflected in hashCode().
0718: if (!(obj instanceof TypeAndValue)) {
0719: return false;
0720: }
0721: TypeAndValue that = (TypeAndValue) obj;
0722: return (type.equalsIgnoreCase(that.type) && (value
0723: .equals(that.value) || getValueComparable().equals(
0724: that.getValueComparable())));
0725: }
0726:
0727: public int hashCode() {
0728: // If two objects are equal, their hash codes must match.
0729: return (type.toUpperCase().hashCode() + getValueComparable()
0730: .hashCode());
0731: }
0732:
0733: /*
0734: * Returns the type.
0735: */
0736: String getType() {
0737: return type;
0738: }
0739:
0740: /*
0741: * Returns the unescaped value.
0742: */
0743: Object getUnescapedValue() {
0744: return unescapeValue(value);
0745: }
0746:
0747: /*
0748: * Returns a canonical representation of "value" suitable for
0749: * comparison using String.compareTo(). If "value" is a string,
0750: * it is returned with escapes and quotes stripped away, and
0751: * hex-encoded UTF-8 converted to 16-bit Unicode chars.
0752: * If value's case is to be ignored, it is returned in uppercase.
0753: * If "value" is binary, it is returned in uppercase but
0754: * otherwise unmodified.
0755: */
0756: private String getValueComparable() {
0757: if (comparable != null) {
0758: return comparable; // return cached result
0759: }
0760:
0761: // cache result
0762: if (binary) {
0763: comparable = value.toUpperCase();
0764: } else {
0765: comparable = (String) unescapeValue(value);
0766: if (!valueCaseSensitive) {
0767: comparable = comparable.toUpperCase(); // ignore case
0768: }
0769: }
0770: return comparable;
0771: }
0772:
0773: /*
0774: * Given the value of an attribute, returns a string suitable
0775: * for inclusion in a DN.
0776: */
0777: static String escapeValue(Object val) {
0778: return (val instanceof byte[]) ? escapeBinaryValue((byte[]) val)
0779: : escapeStringValue((String) val);
0780: }
0781:
0782: /*
0783: * Given the value of a string-valued attribute, returns a
0784: * string suitable for inclusion in a DN. This is accomplished by
0785: * using backslash (\) to escape the following characters:
0786: * leading and trailing whitespace
0787: * , = + < > # ; " \
0788: */
0789: private static String escapeStringValue(String val) {
0790:
0791: final String escapees = ",=+<>#;\"\\";
0792: char[] chars = val.toCharArray();
0793: StringBuffer buf = new StringBuffer(2 * val.length());
0794:
0795: // Find leading and trailing whitespace.
0796: int lead; // index of first char that is not leading whitespace
0797: for (lead = 0; lead < chars.length; lead++) {
0798: if (!isWhitespace(chars[lead])) {
0799: break;
0800: }
0801: }
0802: int trail; // index of last char that is not trailing whitespace
0803: for (trail = chars.length - 1; trail >= 0; trail--) {
0804: if (!isWhitespace(chars[trail])) {
0805: break;
0806: }
0807: }
0808:
0809: for (int i = 0; i < chars.length; i++) {
0810: char c = chars[i];
0811: if ((i < lead) || (i > trail)
0812: || (escapees.indexOf(c) >= 0)) {
0813: buf.append('\\');
0814: }
0815: buf.append(c);
0816: }
0817: return new String(buf);
0818: }
0819:
0820: /*
0821: * Given the value of a binary attribute, returns a string
0822: * suitable for inclusion in a DN (such as "#CEB1DF80").
0823: */
0824: private static String escapeBinaryValue(byte[] val) {
0825:
0826: StringBuffer buf = new StringBuffer(1 + 2 * val.length);
0827: buf.append("#");
0828:
0829: for (int i = 0; i < val.length; i++) {
0830: byte b = val[i];
0831: buf.append(Character.forDigit(0xF & (b >>> 4), 16));
0832: buf.append(Character.forDigit(0xF & b, 16));
0833: }
0834:
0835: return (new String(buf)).toUpperCase();
0836: }
0837:
0838: /*
0839: * Given an attribute value formated according to RFC 2253,
0840: * returns the unformated value. Escapes and quotes are
0841: * stripped away, and hex-encoded UTF-8 is converted to 16-bit
0842: * Unicode chars. Returns a string value as a String, and a
0843: * binary value as a byte array.
0844: */
0845: static Object unescapeValue(String val) {
0846:
0847: char[] chars = val.toCharArray();
0848: int beg = 0;
0849: int end = chars.length;
0850:
0851: // Trim off leading and trailing whitespace.
0852: while ((beg < end) && isWhitespace(chars[beg])) {
0853: ++beg;
0854: }
0855: while ((beg < end) && isWhitespace(chars[end - 1])) {
0856: --end;
0857: }
0858:
0859: // Add back the trailing whitespace with a preceeding '\'
0860: // (escaped or unescaped) that was taken off in the above
0861: // loop. Whether or not to retain this whitespace is
0862: // decided below.
0863: if (end != chars.length && (beg < end)
0864: && chars[end - 1] == '\\') {
0865: end++;
0866: }
0867: if (beg >= end) {
0868: return "";
0869: }
0870:
0871: if (chars[beg] == '#') {
0872: // Value is binary (eg: "#CEB1DF80").
0873: return decodeHexPairs(chars, ++beg, end);
0874: }
0875:
0876: // Trim off quotes.
0877: if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
0878: ++beg;
0879: --end;
0880: }
0881:
0882: StringBuffer buf = new StringBuffer(end - beg);
0883: int esc = -1; // index of the last escaped character
0884:
0885: for (int i = beg; i < end; i++) {
0886: if ((chars[i] == '\\') && (i + 1 < end)) {
0887: if (!Character.isLetterOrDigit(chars[i + 1])) {
0888: ++i; // skip backslash
0889: buf.append(chars[i]); // snarf escaped char
0890: esc = i;
0891: } else {
0892:
0893: // Convert hex-encoded UTF-8 to 16-bit chars.
0894: byte[] utf8 = getUtf8Octets(chars, i, end);
0895: if (utf8.length > 0) {
0896: try {
0897: buf.append(new String(utf8, "UTF8"));
0898: } catch (java.io.UnsupportedEncodingException e) {
0899: // shouldn't happen
0900: }
0901: i += utf8.length * 3 - 1;
0902: } else {
0903: throw new IllegalArgumentException(
0904: "Not a valid attribute string value:"
0905: + val
0906: + ", improper usage of backslash");
0907: }
0908: }
0909: } else {
0910: buf.append(chars[i]); // snarf unescaped char
0911: }
0912: }
0913:
0914: // Get rid of the unescaped trailing whitespace with the
0915: // preceeding '\' character that was previously added back.
0916: int len = buf.length();
0917: if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) {
0918: buf.setLength(len - 1);
0919: }
0920:
0921: return new String(buf);
0922: }
0923:
0924: /*
0925: * Given an array of chars (with starting and ending indexes into it)
0926: * representing bytes encoded as hex-pairs (such as "CEB1DF80"),
0927: * returns a byte array containing the decoded bytes.
0928: */
0929: private static byte[] decodeHexPairs(char[] chars, int beg,
0930: int end) {
0931: byte[] bytes = new byte[(end - beg) / 2];
0932: for (int i = 0; beg + 1 < end; i++) {
0933: int hi = Character.digit(chars[beg], 16);
0934: int lo = Character.digit(chars[beg + 1], 16);
0935: if (hi < 0 || lo < 0) {
0936: break;
0937: }
0938: bytes[i] = (byte) ((hi << 4) + lo);
0939: beg += 2;
0940: }
0941: if (beg != end) {
0942: throw new IllegalArgumentException(
0943: "Illegal attribute value: #"
0944: + new String(chars));
0945: }
0946: return bytes;
0947: }
0948:
0949: /*
0950: * Given an array of chars (with starting and ending indexes into it),
0951: * finds the largest prefix consisting of hex-encoded UTF-8 octets,
0952: * and returns a byte array containing the corresponding UTF-8 octets.
0953: *
0954: * Hex-encoded UTF-8 octets look like this:
0955: * \03\B1\DF\80
0956: */
0957: private static byte[] getUtf8Octets(char[] chars, int beg,
0958: int end) {
0959: byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room
0960: int len = 0; // index of first unused byte in utf8
0961:
0962: while ((beg + 2 < end) && (chars[beg++] == '\\')) {
0963: int hi = Character.digit(chars[beg++], 16);
0964: int lo = Character.digit(chars[beg++], 16);
0965: if (hi < 0 || lo < 0) {
0966: break;
0967: }
0968: utf8[len++] = (byte) ((hi << 4) + lo);
0969: }
0970:
0971: if (len == utf8.length) {
0972: return utf8;
0973: } else {
0974: byte[] res = new byte[len];
0975: System.arraycopy(utf8, 0, res, 0, len);
0976: return res;
0977: }
0978: }
0979: }
0980:
0981: /*
0982: * For testing.
0983: */
0984: /*
0985: public static void main(String[] args) {
0986:
0987: try {
0988: if (args.length == 1) { // parse and print components
0989: LdapName n = new LdapName(args[0]);
0990:
0991: Enumeration rdns = n.rdns.elements();
0992: while (rdns.hasMoreElements()) {
0993: Rdn rdn = (Rdn)rdns.nextElement();
0994: for (int i = 0; i < rdn.tvs.size(); i++) {
0995: System.out.print("[" + rdn.tvs.elementAt(i) + "]");
0996: }
0997: System.out.println();
0998: }
0999:
1000: } else { // compare two names
1001: LdapName n1 = new LdapName(args[0]);
1002: LdapName n2 = new LdapName(args[1]);
1003: n1.unparsed = null;
1004: n2.unparsed = null;
1005: boolean eq = n1.equals(n2);
1006: System.out.println("[" + n1 + (eq ? "] == [" : "] != [")
1007: + n2 + "]");
1008: }
1009: } catch (Exception e) {
1010: e.printStackTrace();
1011: }
1012: }
1013: */
1014: }
|