Path: blob/master/src/java.naming/share/classes/javax/naming/ldap/LdapName.java
41159 views
/*1* Copyright (c) 2003, 2020, 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 javax.naming.ldap;2627import javax.naming.Name;28import javax.naming.InvalidNameException;2930import java.util.Enumeration;31import java.util.Collection;32import java.util.ArrayList;33import java.util.List;34import java.util.Iterator;35import java.util.ListIterator;36import java.util.Collections;3738import java.io.ObjectOutputStream;39import java.io.ObjectInputStream;40import java.io.IOException;4142/**43* This class represents a distinguished name as specified by44* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.45* A distinguished name, or DN, is composed of an ordered list of46* components called <em>relative distinguished name</em>s, or RDNs.47* Details of a DN's syntax are described in RFC 2253.48*<p>49* This class resolves a few ambiguities found in RFC 225350* as follows:51* <ul>52* <li> RFC 2253 leaves the term "whitespace" undefined. The53* ASCII space character 0x20 (" ") is used in its place.54* <li> Whitespace is allowed on either side of ',', ';', '=', and '+'.55* Such whitespace is accepted but not generated by this code,56* and is ignored when comparing names.57* <li> AttributeValue strings containing '=' or non-leading '#'58* characters (unescaped) are accepted.59* </ul>60*<p>61* String names passed to {@code LdapName} or returned by it62* use the full Unicode character set. They may also contain63* characters encoded into UTF-8 with each octet represented by a64* three-character substring such as "\\B4".65* They may not, however, contain characters encoded into UTF-8 with66* each octet represented by a single character in the string: the67* meaning would be ambiguous.68*<p>69* {@code LdapName} will properly parse all valid names, but70* does not attempt to detect all possible violations when parsing71* invalid names. It is "generous" in accepting invalid names.72* The "validity" of a name is determined ultimately when it73* is supplied to an LDAP server, which may accept or74* reject the name based on factors such as its schema information75* and interoperability considerations.76*<p>77* When names are tested for equality, attribute types, both binary78* and string values, are case-insensitive.79* String values with different but equivalent usage of quoting,80* escaping, or UTF8-hex-encoding are considered equal. The order of81* components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not82* significant.83* <p>84* The components of a LDAP name, that is, RDNs, are numbered. The85* indexes of a LDAP name with n RDNs range from 0 to n-1.86* This range may be written as [0,n).87* The right most RDN is at index 0, and the left most RDN is at88* index n-1. For example, the distinguished name:89* "CN=Steve Kille, O=Isode Limited, C=GB" is numbered in the following90* sequence ranging from 0 to 2: {C=GB, O=Isode Limited, CN=Steve Kille}. An91* empty LDAP name is represented by an empty RDN list.92*<p>93* Concurrent multithreaded read-only access of an instance of94* {@code LdapName} need not be synchronized.95*<p>96* Unless otherwise noted, the behavior of passing a null argument97* to a constructor or method in this class will cause a98* NullPointerException to be thrown.99*100* @author Scott Seligman101* @since 1.5102*/103104public class LdapName implements Name {105106private transient List<Rdn> rdns; // parsed name components107private transient String unparsed; // if non-null, the DN in unparsed form108109@java.io.Serial110private static final long serialVersionUID = -1595520034788997356L;111112/**113* Constructs an LDAP name from the given distinguished name.114*115* @param name This is a non-null distinguished name formatted116* according to the rules defined in117* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.118*119* @throws InvalidNameException if a syntax violation is detected.120* @see Rdn#escapeValue(Object value)121*/122public LdapName(String name) throws InvalidNameException {123unparsed = name;124parse();125}126127/**128* Constructs an LDAP name given its parsed RDN components.129* <p>130* The indexing of RDNs in the list follows the numbering of131* RDNs described in the class description.132*133* @param rdns The non-null list of {@code Rdn}s forming this LDAP name.134*/135public LdapName(List<Rdn> rdns) {136137// if (rdns instanceof ArrayList<Rdn>) {138// this.rdns = rdns.clone();139// } else if (rdns instanceof List<Rdn>) {140// this.rdns = new ArrayList<Rdn>(rdns);141// } else {142// throw IllegalArgumentException(143// "Invalid entries, list entries must be of type Rdn");144// }145146this.rdns = new ArrayList<>(rdns.size());147for (int i = 0; i < rdns.size(); i++) {148Object obj = rdns.get(i);149if (!(obj instanceof Rdn)) {150throw new IllegalArgumentException("Entry:" + obj +151" not a valid type;list entries must be of type Rdn");152}153this.rdns.add((Rdn)obj);154}155}156157/*158* Constructs an LDAP name given its parsed components (the elements159* of "rdns" in the range [beg,end)) and, optionally160* (if "name" is not null), the unparsed DN.161*162*/163private LdapName(String name, List<Rdn> rdns, int beg, int end) {164unparsed = name;165// this.rdns = rdns.subList(beg, end);166167List<Rdn> sList = rdns.subList(beg, end);168this.rdns = new ArrayList<>(sList);169}170171/**172* Retrieves the number of components in this LDAP name.173* @return The non-negative number of components in this LDAP name.174*/175public int size() {176return rdns.size();177}178179/**180* Determines whether this LDAP name is empty.181* An empty name is one with zero components.182* @return true if this LDAP name is empty, false otherwise.183*/184public boolean isEmpty() {185return rdns.isEmpty();186}187188/**189* Retrieves the components of this name as an enumeration190* of strings. The effect of updates to this name on this enumeration191* is undefined. If the name has zero components, an empty (non-null)192* enumeration is returned.193* The order of the components returned by the enumeration is same as194* the order in which the components are numbered as described in the195* class description.196*197* @return A non-null enumeration of the components of this LDAP name.198* Each element of the enumeration is of class String.199*/200public Enumeration<String> getAll() {201final Iterator<Rdn> iter = rdns.iterator();202203return new Enumeration<String>() {204public boolean hasMoreElements() {205return iter.hasNext();206}207public String nextElement() {208return iter.next().toString();209}210};211}212213/**214* Retrieves a component of this LDAP name as a string.215* @param posn The 0-based index of the component to retrieve.216* Must be in the range [0,size()).217* @return The non-null component at index posn.218* @exception IndexOutOfBoundsException if posn is outside the219* specified range.220*/221public String get(int posn) {222return rdns.get(posn).toString();223}224225/**226* Retrieves an RDN of this LDAP name as an Rdn.227* @param posn The 0-based index of the RDN to retrieve.228* Must be in the range [0,size()).229* @return The non-null RDN at index posn.230* @exception IndexOutOfBoundsException if posn is outside the231* specified range.232*/233public Rdn getRdn(int posn) {234return rdns.get(posn);235}236237/**238* Creates a name whose components consist of a prefix of the239* components of this LDAP name.240* Subsequent changes to this name will not affect the name241* that is returned and vice versa.242* @param posn The 0-based index of the component at which to stop.243* Must be in the range [0,size()].244* @return An instance of {@code LdapName} consisting of the245* components at indexes in the range [0,posn).246* If posn is zero, an empty LDAP name is returned.247* @exception IndexOutOfBoundsException248* If posn is outside the specified range.249*/250public Name getPrefix(int posn) {251try {252return new LdapName(null, rdns, 0, posn);253} catch (IllegalArgumentException e) {254throw new IndexOutOfBoundsException(255"Posn: " + posn + ", Size: "+ rdns.size());256}257}258259/**260* Creates a name whose components consist of a suffix of the261* components in this LDAP name.262* Subsequent changes to this name do not affect the name that is263* returned and vice versa.264*265* @param posn The 0-based index of the component at which to start.266* Must be in the range [0,size()].267* @return An instance of {@code LdapName} consisting of the268* components at indexes in the range [posn,size()).269* If posn is equal to size(), an empty LDAP name is270* returned.271* @exception IndexOutOfBoundsException272* If posn is outside the specified range.273*/274public Name getSuffix(int posn) {275try {276return new LdapName(null, rdns, posn, rdns.size());277} catch (IllegalArgumentException e) {278throw new IndexOutOfBoundsException(279"Posn: " + posn + ", Size: "+ rdns.size());280}281}282283/**284* Determines whether this LDAP name starts with a specified LDAP name285* prefix.286* A name {@code n} is a prefix if it is equal to287* {@code getPrefix(n.size())}--in other words this LDAP288* name starts with 'n'. If n is null or not a RFC2253 formatted name289* as described in the class description, false is returned.290*291* @param n The LDAP name to check.292* @return true if {@code n} is a prefix of this LDAP name,293* false otherwise.294* @see #getPrefix(int posn)295*/296public boolean startsWith(Name n) {297if (n == null) {298return false;299}300int len1 = rdns.size();301int len2 = n.size();302return (len1 >= len2 &&303matches(0, len2, n));304}305306/**307* Determines whether the specified RDN sequence forms a prefix of this308* LDAP name. Returns true if this LdapName is at least as long as rdns,309* and for every position p in the range [0, rdns.size()) the component310* getRdn(p) matches rdns.get(p). Returns false otherwise. If rdns is311* null, false is returned.312*313* @param rdns The sequence of {@code Rdn}s to check.314* @return true if {@code rdns} form a prefix of this LDAP name,315* false otherwise.316*/317public boolean startsWith(List<Rdn> rdns) {318if (rdns == null) {319return false;320}321int len1 = this.rdns.size();322int len2 = rdns.size();323return (len1 >= len2 &&324doesListMatch(0, len2, rdns));325}326327/**328* Determines whether this LDAP name ends with a specified329* LDAP name suffix.330* A name {@code n} is a suffix if it is equal to331* {@code getSuffix(size()-n.size())}--in other words this LDAP332* name ends with 'n'. If n is null or not a RFC2253 formatted name333* as described in the class description, false is returned.334*335* @param n The LDAP name to check.336* @return true if {@code n} is a suffix of this name, false otherwise.337* @see #getSuffix(int posn)338*/339public boolean endsWith(Name n) {340if (n == null) {341return false;342}343int len1 = rdns.size();344int len2 = n.size();345return (len1 >= len2 &&346matches(len1 - len2, len1, n));347}348349/**350* Determines whether the specified RDN sequence forms a suffix of this351* LDAP name. Returns true if this LdapName is at least as long as rdns,352* and for every position p in the range [size() - rdns.size(), size())353* the component getRdn(p) matches rdns.get(p). Returns false otherwise.354* If rdns is null, false is returned.355*356* @param rdns The sequence of {@code Rdn}s to check.357* @return true if {@code rdns} form a suffix of this LDAP name,358* false otherwise.359*/360public boolean endsWith(List<Rdn> rdns) {361if (rdns == null) {362return false;363}364int len1 = this.rdns.size();365int len2 = rdns.size();366return (len1 >= len2 &&367doesListMatch(len1 - len2, len1, rdns));368}369370private boolean doesListMatch(int beg, int end, List<Rdn> rdns) {371for (int i = beg; i < end; i++) {372if (!this.rdns.get(i).equals(rdns.get(i - beg))) {373return false;374}375}376return true;377}378379/*380* Helper method for startsWith() and endsWith().381* Returns true if components [beg,end) match the components of "n".382* If "n" is not an LdapName, each of its components is parsed as383* the string form of an RDN.384* The following must hold: end - beg == n.size().385*/386private boolean matches(int beg, int end, Name n) {387if (n instanceof LdapName) {388LdapName ln = (LdapName) n;389return doesListMatch(beg, end, ln.rdns);390} else {391for (int i = beg; i < end; i++) {392Rdn rdn;393String rdnString = n.get(i - beg);394try {395rdn = (new Rfc2253Parser(rdnString)).parseRdn();396} catch (InvalidNameException e) {397return false;398}399if (!rdn.equals(rdns.get(i))) {400return false;401}402}403}404return true;405}406407/**408* Adds the components of a name -- in order -- to the end of this name.409*410* @param suffix The non-null components to add.411* @return The updated name (not a new instance).412*413* @throws InvalidNameException if {@code suffix} is not a valid LDAP414* name, or if the addition of the components would violate the415* syntax rules of this LDAP name.416*/417public Name addAll(Name suffix) throws InvalidNameException {418return addAll(size(), suffix);419}420421422/**423* Adds the RDNs of a name -- in order -- to the end of this name.424*425* @param suffixRdns The non-null suffix {@code Rdn}s to add.426* @return The updated name (not a new instance).427*/428public Name addAll(List<Rdn> suffixRdns) {429return addAll(size(), suffixRdns);430}431432/**433* Adds the components of a name -- in order -- at a specified position434* within this name. Components of this LDAP name at or after the435* index (if any) of the first new component are shifted up436* (away from index 0) to accommodate the new components.437*438* @param suffix The non-null components to add.439* @param posn The index at which to add the new component.440* Must be in the range [0,size()].441*442* @return The updated name (not a new instance).443*444* @throws InvalidNameException if {@code suffix} is not a valid LDAP445* name, or if the addition of the components would violate the446* syntax rules of this LDAP name.447* @throws IndexOutOfBoundsException448* If posn is outside the specified range.449*/450public Name addAll(int posn, Name suffix)451throws InvalidNameException {452unparsed = null; // no longer valid453if (suffix instanceof LdapName) {454LdapName s = (LdapName) suffix;455rdns.addAll(posn, s.rdns);456} else {457Enumeration<String> comps = suffix.getAll();458while (comps.hasMoreElements()) {459rdns.add(posn++,460(new Rfc2253Parser(comps.nextElement()).461parseRdn()));462}463}464return this;465}466467/**468* Adds the RDNs of a name -- in order -- at a specified position469* within this name. RDNs of this LDAP name at or after the470* index (if any) of the first new RDN are shifted up (away from index 0) to471* accommodate the new RDNs.472*473* @param suffixRdns The non-null suffix {@code Rdn}s to add.474* @param posn The index at which to add the suffix RDNs.475* Must be in the range [0,size()].476*477* @return The updated name (not a new instance).478* @throws IndexOutOfBoundsException479* If posn is outside the specified range.480*/481public Name addAll(int posn, List<Rdn> suffixRdns) {482unparsed = null;483for (int i = 0; i < suffixRdns.size(); i++) {484Object obj = suffixRdns.get(i);485if (!(obj instanceof Rdn)) {486throw new IllegalArgumentException("Entry:" + obj +487" not a valid type;suffix list entries must be of type Rdn");488}489rdns.add(i + posn, (Rdn)obj);490}491return this;492}493494/**495* Adds a single component to the end of this LDAP name.496*497* @param comp The non-null component to add.498* @return The updated LdapName, not a new instance.499* Cannot be null.500* @exception InvalidNameException If adding comp at end of the name501* would violate the name's syntax.502*/503public Name add(String comp) throws InvalidNameException {504return add(size(), comp);505}506507/**508* Adds a single RDN to the end of this LDAP name.509*510* @param comp The non-null RDN to add.511*512* @return The updated LdapName, not a new instance.513* Cannot be null.514*/515public Name add(Rdn comp) {516return add(size(), comp);517}518519/**520* Adds a single component at a specified position within this521* LDAP name.522* Components of this LDAP name at or after the index (if any) of the new523* component are shifted up by one (away from index 0) to accommodate524* the new component.525*526* @param comp The non-null component to add.527* @param posn The index at which to add the new component.528* Must be in the range [0,size()].529* @return The updated LdapName, not a new instance.530* Cannot be null.531* @exception IndexOutOfBoundsException532* If posn is outside the specified range.533* @exception InvalidNameException If adding comp at the534* specified position would violate the name's syntax.535*/536public Name add(int posn, String comp) throws InvalidNameException {537Rdn rdn = (new Rfc2253Parser(comp)).parseRdn();538rdns.add(posn, rdn);539unparsed = null; // no longer valid540return this;541}542543/**544* Adds a single RDN at a specified position within this545* LDAP name.546* RDNs of this LDAP name at or after the index (if any) of the new547* RDN are shifted up by one (away from index 0) to accommodate548* the new RDN.549*550* @param comp The non-null RDN to add.551* @param posn The index at which to add the new RDN.552* Must be in the range [0,size()].553* @return The updated LdapName, not a new instance.554* Cannot be null.555* @exception IndexOutOfBoundsException556* If posn is outside the specified range.557*/558public Name add(int posn, Rdn comp) {559if (comp == null) {560throw new NullPointerException("Cannot set comp to null");561}562rdns.add(posn, comp);563unparsed = null; // no longer valid564return this;565}566567/**568* Removes a component from this LDAP name.569* The component of this name at the specified position is removed.570* Components with indexes greater than this position (if any)571* are shifted down (toward index 0) by one.572*573* @param posn The index of the component to remove.574* Must be in the range [0,size()).575* @return The component removed (a String).576*577* @throws IndexOutOfBoundsException578* if posn is outside the specified range.579* @throws InvalidNameException if deleting the component580* would violate the syntax rules of the name.581*/582public Object remove(int posn) throws InvalidNameException {583unparsed = null; // no longer valid584return rdns.remove(posn).toString();585}586587/**588* Retrieves the list of relative distinguished names.589* The contents of the list are unmodifiable.590* The indexing of RDNs in the returned list follows the numbering of591* RDNs as described in the class description.592* If the name has zero components, an empty list is returned.593*594* @return The name as a list of RDNs which are instances of595* the class {@link Rdn Rdn}.596*/597public List<Rdn> getRdns() {598return Collections.unmodifiableList(rdns);599}600601/**602* Generates a new copy of this name.603* Subsequent changes to the components of this name will not604* affect the new copy, and vice versa.605*606* @return A copy of the this LDAP name.607*/608public Object clone() {609return new LdapName(unparsed, rdns, 0, rdns.size());610}611612/**613* Returns a string representation of this LDAP name in a format614* defined by <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>615* and described in the class description. If the name has zero616* components an empty string is returned.617*618* @return The string representation of the LdapName.619*/620public String toString() {621if (unparsed != null) {622return unparsed;623}624StringBuilder builder = new StringBuilder();625int size = rdns.size();626if ((size - 1) >= 0) {627builder.append(rdns.get(size - 1));628}629for (int next = size - 2; next >= 0; next--) {630builder.append(',');631builder.append(rdns.get(next));632}633unparsed = builder.toString();634return unparsed;635}636637/**638* Determines whether two LDAP names are equal.639* If obj is null or not an LDAP name, false is returned.640* <p>641* Two LDAP names are equal if each RDN in one is equal642* to the corresponding RDN in the other. This implies643* both have the same number of RDNs, and each RDN's644* equals() test against the corresponding RDN in the other645* name returns true. See {@link Rdn#equals(Object obj)}646* for a definition of RDN equality.647*648* @param obj The possibly null object to compare against.649* @return true if obj is equal to this LDAP name,650* false otherwise.651* @see #hashCode652*/653public boolean equals(Object obj) {654// check possible shortcuts655if (obj == this) {656return true;657}658if (!(obj instanceof LdapName)) {659return false;660}661LdapName that = (LdapName) obj;662if (rdns.size() != that.rdns.size()) {663return false;664}665if (unparsed != null && unparsed.equalsIgnoreCase(666that.unparsed)) {667return true;668}669// Compare RDNs one by one for equality670for (int i = 0; i < rdns.size(); i++) {671// Compare a single pair of RDNs.672Rdn rdn1 = rdns.get(i);673Rdn rdn2 = that.rdns.get(i);674if (!rdn1.equals(rdn2)) {675return false;676}677}678return true;679}680681/**682* Compares this LdapName with the specified Object for order.683* Returns a negative integer, zero, or a positive integer as this684* Name is less than, equal to, or greater than the given Object.685* <p>686* If obj is null or not an instance of LdapName, ClassCastException687* is thrown.688* <p>689* Ordering of LDAP names follows the lexicographical rules for690* string comparison, with the extension that this applies to all691* the RDNs in the LDAP name. All the RDNs are lined up in their692* specified order and compared lexicographically.693* See {@link Rdn#compareTo(Object obj) Rdn.compareTo(Object obj)}694* for RDN comparison rules.695* <p>696* If this LDAP name is lexicographically lesser than obj,697* a negative number is returned.698* If this LDAP name is lexicographically greater than obj,699* a positive number is returned.700* @param obj The non-null LdapName instance to compare against.701*702* @return A negative integer, zero, or a positive integer as this Name703* is less than, equal to, or greater than the given obj.704* @exception ClassCastException if obj is null or not a LdapName.705*/706public int compareTo(Object obj) {707708if (!(obj instanceof LdapName)) {709throw new ClassCastException("The obj is not a LdapName");710}711712// check possible shortcuts713if (obj == this) {714return 0;715}716LdapName that = (LdapName) obj;717718if (unparsed != null && unparsed.equalsIgnoreCase(719that.unparsed)) {720return 0;721}722723// Compare RDNs one by one, lexicographically.724int minSize = Math.min(rdns.size(), that.rdns.size());725for (int i = 0; i < minSize; i++) {726// Compare a single pair of RDNs.727Rdn rdn1 = rdns.get(i);728Rdn rdn2 = that.rdns.get(i);729730int diff = rdn1.compareTo(rdn2);731if (diff != 0) {732return diff;733}734}735return (rdns.size() - that.rdns.size()); // longer DN wins736}737738/**739* Computes the hash code of this LDAP name.740* The hash code is the sum of the hash codes of individual RDNs741* of this name.742*743* @return An int representing the hash code of this name.744* @see #equals745*/746public int hashCode() {747// Sum up the hash codes of the components.748int hash = 0;749750// For each RDN...751for (int i = 0; i < rdns.size(); i++) {752Rdn rdn = rdns.get(i);753hash += rdn.hashCode();754}755return hash;756}757758/**759* The writeObject method is called to save the state of the760* {@code LdapName} to a stream.761*762* Serializes only the unparsed DN, for compactness and to avoid763* any implementation dependency.764*765* @serialData The DN {@code String} representation of this LDAP name.766*767* @param s the {@code ObjectOutputStream} to write to768* @throws java.io.IOException if an I/O error occurs769*/770@java.io.Serial771private void writeObject(ObjectOutputStream s)772throws java.io.IOException {773s.defaultWriteObject();774s.writeObject(toString());775}776777/**778* The readObject method is called to restore the state of779* the {@code LdapName} from a stream.780*781* See {@code writeObject} for a description of the serial form.782*783* @param s the {@code ObjectInputStream} to read from784* @throws java.io.IOException if an I/O error occurs785* @throws ClassNotFoundException if the class of a serialized object786* could not be found787*/788@java.io.Serial789private void readObject(ObjectInputStream s)790throws java.io.IOException, ClassNotFoundException {791s.defaultReadObject();792unparsed = (String)s.readObject();793try {794parse();795} catch (InvalidNameException e) {796// shouldn't happen797throw new java.io.StreamCorruptedException(798"Invalid name: " + unparsed);799}800}801802private void parse() throws InvalidNameException {803// rdns = (ArrayList<Rdn>) (new RFC2253Parser(unparsed)).getDN();804805rdns = new Rfc2253Parser(unparsed).parseDn();806}807}808809810