1017 lines · 33.7 KB · Java
RawDownload
/* * Copyright (c) 1999, 2004, 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 javax.naming.*;import javax.naming.directory.Attributes;import javax.naming.directory.Attribute;import javax.naming.directory.BasicAttributes;  /** * <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 rdns;      // parsed name components    private transient boolean valuesCaseSensitive = false;     /**     * 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.     */    private LdapName(String name, Vector rdns) {        unparsed = name;        this.rdns = (Vector)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 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 = (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 = (Rdn)rdns.elementAt(i);            Rdn rdn2 = (Rdn)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 = (Rdn)rdns.elementAt(i);            hash += rdn.hashCode();        }        return hash;    }     public int size() {        return rdns.size();    }     public boolean isEmpty() {        return rdns.isEmpty();    }     public Enumeration getAll() {        final Enumeration enum_ = rdns.elements();         return new Enumeration () {            public boolean hasMoreElements() {                return enum_.hasMoreElements();            }            public Object 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.     */     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 = (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 comps = suffix.getAll();            while (comps.hasMoreElements()) {                DnParser p = new DnParser((String)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:     *<ul>     *<li>leading and trailing whitespace     *<li><pre>, = + < > # ; " \</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 formated according to RFC 2253,     * returns the unformated 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.     */    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 getDn() throws InvalidNameException {            cur = 0;            Vector 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);        }         /*

Lines 1–500 of 1017.