Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/javax/naming/ldap/Rdn.java
38918 views
/*1* Copyright (c) 2003, 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 javax.naming.ldap;2627import java.util.Iterator;28import java.util.NoSuchElementException;29import java.util.ArrayList;30import java.util.Locale;31import java.util.Collections;3233import javax.naming.InvalidNameException;34import javax.naming.directory.BasicAttributes;35import javax.naming.directory.Attributes;36import javax.naming.directory.Attribute;37import javax.naming.NamingEnumeration;38import javax.naming.NamingException;3940import java.io.Serializable;41import java.io.ObjectOutputStream;42import java.io.ObjectInputStream;43import java.io.IOException;4445/**46* This class represents a relative distinguished name, or RDN, which is a47* component of a distinguished name as specified by48* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.49* An example of an RDN is "OU=Sales+CN=J.Smith". In this example,50* the RDN consist of multiple attribute type/value pairs. The51* RDN is parsed as described in the class description for52* {@link javax.naming.ldap.LdapName <tt>LdapName</tt>}.53* <p>54* The Rdn class represents an RDN as attribute type/value mappings,55* which can be viewed using56* {@link javax.naming.directory.Attributes Attributes}.57* In addition, it contains convenience methods that allow easy retrieval58* of type and value when the Rdn consist of a single type/value pair,59* which is how it appears in a typical usage.60* It also contains helper methods that allow escaping of the unformatted61* attribute value and unescaping of the value formatted according to the62* escaping syntax defined in RFC2253. For methods that take or return63* attribute value as an Object, the value is either a String64* (in unescaped form) or a byte array.65* <p>66* <code>Rdn</code> will properly parse all valid RDNs, but67* does not attempt to detect all possible violations when parsing68* invalid RDNs. It is "generous" in accepting invalid RDNs.69* The "validity" of a name is determined ultimately when it70* is supplied to an LDAP server, which may accept or71* reject the name based on factors such as its schema information72* and interoperability considerations.73*74* <p>75* The following code example shows how to construct an Rdn using the76* constructor that takes type and value as arguments:77* <pre>78* Rdn rdn = new Rdn("cn", "Juicy, Fruit");79* System.out.println(rdn.toString());80* </pre>81* The last line will print <tt>cn=Juicy\, Fruit</tt>. The82* {@link #unescapeValue(String) <tt>unescapeValue()</tt>} method can be83* used to unescape the escaped comma resulting in the original84* value <tt>"Juicy, Fruit"</tt>. The {@link #escapeValue(Object)85* <tt>escapeValue()</tt>} method adds the escape back preceding the comma.86* <p>87* This class can be instantiated by a string representation88* of the RDN defined in RFC 2253 as shown in the following code example:89* <pre>90* Rdn rdn = new Rdn("cn=Juicy\\, Fruit");91* System.out.println(rdn.toString());92* </pre>93* The last line will print <tt>cn=Juicy\, Fruit</tt>.94* <p>95* Concurrent multithreaded read-only access of an instance of96* <tt>Rdn</tt> need not be synchronized.97* <p>98* Unless otherwise noted, the behavior of passing a null argument99* to a constructor or method in this class will cause NullPointerException100* to be thrown.101*102* @since 1.5103*/104105public class Rdn implements Serializable, Comparable<Object> {106107private transient ArrayList<RdnEntry> entries;108109// The common case.110private static final int DEFAULT_SIZE = 1;111112private static final long serialVersionUID = -5994465067210009656L;113114/**115* Constructs an Rdn from the given attribute set. See116* {@link javax.naming.directory.Attributes Attributes}.117* <p>118* The string attribute values are not interpreted as119* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>120* formatted RDN strings. That is, the values are used121* literally (not parsed) and assumed to be unescaped.122*123* @param attrSet The non-null and non-empty attributes containing124* type/value mappings.125* @throws InvalidNameException If contents of <tt>attrSet</tt> cannot126* be used to construct a valid RDN.127*/128public Rdn(Attributes attrSet) throws InvalidNameException {129if (attrSet.size() == 0) {130throw new InvalidNameException("Attributes cannot be empty");131}132entries = new ArrayList<>(attrSet.size());133NamingEnumeration<? extends Attribute> attrs = attrSet.getAll();134try {135for (int nEntries = 0; attrs.hasMore(); nEntries++) {136RdnEntry entry = new RdnEntry();137Attribute attr = attrs.next();138entry.type = attr.getID();139entry.value = attr.get();140entries.add(nEntries, entry);141}142} catch (NamingException e) {143InvalidNameException e2 = new InvalidNameException(144e.getMessage());145e2.initCause(e);146throw e2;147}148sort(); // arrange entries for comparison149}150151/**152* Constructs an Rdn from the given string.153* This constructor takes a string formatted according to the rules154* defined in <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>155* and described in the class description for156* {@link javax.naming.ldap.LdapName}.157*158* @param rdnString The non-null and non-empty RFC2253 formatted string.159* @throws InvalidNameException If a syntax error occurs during160* parsing of the rdnString.161*/162public Rdn(String rdnString) throws InvalidNameException {163entries = new ArrayList<>(DEFAULT_SIZE);164(new Rfc2253Parser(rdnString)).parseRdn(this);165}166167/**168* Constructs an Rdn from the given <tt>rdn</tt>.169* The contents of the <tt>rdn</tt> are simply copied into the newly170* created Rdn.171* @param rdn The non-null Rdn to be copied.172*/173public Rdn(Rdn rdn) {174entries = new ArrayList<>(rdn.entries.size());175entries.addAll(rdn.entries);176}177178/**179* Constructs an Rdn from the given attribute type and180* value.181* The string attribute values are not interpreted as182* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>183* formatted RDN strings. That is, the values are used184* literally (not parsed) and assumed to be unescaped.185*186* @param type The non-null and non-empty string attribute type.187* @param value The non-null and non-empty attribute value.188* @throws InvalidNameException If type/value cannot be used to189* construct a valid RDN.190* @see #toString()191*/192public Rdn(String type, Object value) throws InvalidNameException {193if (value == null) {194throw new NullPointerException("Cannot set value to null");195}196if (type.equals("") || isEmptyValue(value)) {197throw new InvalidNameException(198"type or value cannot be empty, type:" + type +199" value:" + value);200}201entries = new ArrayList<>(DEFAULT_SIZE);202put(type, value);203}204205private boolean isEmptyValue(Object val) {206return ((val instanceof String) && val.equals("")) ||207((val instanceof byte[]) && (((byte[]) val).length == 0));208}209210// An empty constructor used by the parser211Rdn() {212entries = new ArrayList<>(DEFAULT_SIZE);213}214215/*216* Adds the given attribute type and value to this Rdn.217* The string attribute values are not interpreted as218* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>219* formatted RDN strings. That is the values are used220* literally (not parsed) and assumed to be unescaped.221*222* @param type The non-null and non-empty string attribute type.223* @param value The non-null and non-empty attribute value.224* @return The updated Rdn, not a new one. Cannot be null.225* @see #toString()226*/227Rdn put(String type, Object value) {228229// create new Entry230RdnEntry newEntry = new RdnEntry();231newEntry.type = type;232if (value instanceof byte[]) { // clone the byte array233newEntry.value = ((byte[]) value).clone();234} else {235newEntry.value = value;236}237entries.add(newEntry);238return this;239}240241void sort() {242if (entries.size() > 1) {243Collections.sort(entries);244}245}246247/**248* Retrieves one of this Rdn's value.249* This is a convenience method for obtaining the value,250* when the RDN contains a single type and value mapping,251* which is the common RDN usage.252* <p>253* For a multi-valued RDN, this method returns value corresponding254* to the type returned by {@link #getType() getType()} method.255*256* @return The non-null attribute value.257*/258public Object getValue() {259return entries.get(0).getValue();260}261262/**263* Retrieves one of this Rdn's type.264* This is a convenience method for obtaining the type,265* when the RDN contains a single type and value mapping,266* which is the common RDN usage.267* <p>268* For a multi-valued RDN, the type/value pairs have269* no specific order defined on them. In that case, this method270* returns type of one of the type/value pairs.271* The {@link #getValue() getValue()} method returns the272* value corresponding to the type returned by this method.273*274* @return The non-null attribute type.275*/276public String getType() {277return entries.get(0).getType();278}279280/**281* Returns this Rdn as a string represented in a format defined by282* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a> and described283* in the class description for {@link javax.naming.ldap.LdapName LdapName}.284*285* @return The string representation of the Rdn.286*/287public String toString() {288StringBuilder builder = new StringBuilder();289int size = entries.size();290if (size > 0) {291builder.append(entries.get(0));292}293for (int next = 1; next < size; next++) {294builder.append('+');295builder.append(entries.get(next));296}297return builder.toString();298}299300/**301* Compares this Rdn with the specified Object for order.302* Returns a negative integer, zero, or a positive integer as this303* Rdn is less than, equal to, or greater than the given Object.304* <p>305* If obj is null or not an instance of Rdn, ClassCastException306* is thrown.307* <p>308* The attribute type and value pairs of the RDNs are lined up309* against each other and compared lexicographically. The order of310* components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not311* significant.312*313* @param obj The non-null object to compare against.314* @return A negative integer, zero, or a positive integer as this Rdn315* is less than, equal to, or greater than the given Object.316* @exception ClassCastException if obj is null or not a Rdn.317*/318public int compareTo(Object obj) {319if (!(obj instanceof Rdn)) {320throw new ClassCastException("The obj is not a Rdn");321}322if (obj == this) {323return 0;324}325Rdn that = (Rdn) obj;326int minSize = Math.min(entries.size(), that.entries.size());327for (int i = 0; i < minSize; i++) {328329// Compare a single pair of type/value pairs.330int diff = entries.get(i).compareTo(that.entries.get(i));331if (diff != 0) {332return diff;333}334}335return (entries.size() - that.entries.size()); // longer RDN wins336}337338/**339* Compares the specified Object with this Rdn for equality.340* Returns true if the given object is also a Rdn and the two Rdns341* represent the same attribute type and value mappings. The order of342* components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not343* significant.344* <p>345* Type and value equality matching is done as below:346* <ul>347* <li> The types are compared for equality with their case ignored.348* <li> String values with different but equivalent usage of quoting,349* escaping, or UTF8-hex-encoding are considered equal.350* The case of the values is ignored during the comparison.351* </ul>352* <p>353* If obj is null or not an instance of Rdn, false is returned.354* <p>355* @param obj object to be compared for equality with this Rdn.356* @return true if the specified object is equal to this Rdn.357* @see #hashCode()358*/359public boolean equals(Object obj) {360if (obj == this) {361return true;362}363if (!(obj instanceof Rdn)) {364return false;365}366Rdn that = (Rdn) obj;367if (entries.size() != that.size()) {368return false;369}370for (int i = 0; i < entries.size(); i++) {371if (!entries.get(i).equals(that.entries.get(i))) {372return false;373}374}375return true;376}377378/**379* Returns the hash code of this RDN. Two RDNs that are380* equal (according to the equals method) will have the same381* hash code.382*383* @return An int representing the hash code of this Rdn.384* @see #equals385*/386public int hashCode() {387388// Sum up the hash codes of the components.389int hash = 0;390391// For each type/value pair...392for (int i = 0; i < entries.size(); i++) {393hash += entries.get(i).hashCode();394}395return hash;396}397398/**399* Retrieves the {@link javax.naming.directory.Attributes Attributes}400* view of the type/value mappings contained in this Rdn.401*402* @return The non-null attributes containing the type/value403* mappings of this Rdn.404*/405public Attributes toAttributes() {406Attributes attrs = new BasicAttributes(true);407for (int i = 0; i < entries.size(); i++) {408RdnEntry entry = entries.get(i);409Attribute attr = attrs.put(entry.getType(), entry.getValue());410if (attr != null) {411attr.add(entry.getValue());412attrs.put(attr);413}414}415return attrs;416}417418419private static class RdnEntry implements Comparable<RdnEntry> {420private String type;421private Object value;422423// If non-null, a cannonical representation of the value suitable424// for comparison using String.compareTo()425private String comparable = null;426427String getType() {428return type;429}430431Object getValue() {432return value;433}434435public int compareTo(RdnEntry that) {436int diff = type.compareToIgnoreCase(that.type);437if (diff != 0) {438return diff;439}440if (value.equals(that.value)) { // try shortcut441return 0;442}443return getValueComparable().compareTo(444that.getValueComparable());445}446447public boolean equals(Object obj) {448if (obj == this) {449return true;450}451if (!(obj instanceof RdnEntry)) {452return false;453}454455// Any change here must be reflected in hashCode()456RdnEntry that = (RdnEntry) obj;457return (type.equalsIgnoreCase(that.type)) &&458(getValueComparable().equals(459that.getValueComparable()));460}461462public int hashCode() {463return (type.toUpperCase(Locale.ENGLISH).hashCode() +464getValueComparable().hashCode());465}466467public String toString() {468return type + "=" + escapeValue(value);469}470471private String getValueComparable() {472if (comparable != null) {473return comparable; // return cached result474}475476// cache result477if (value instanceof byte[]) {478comparable = escapeBinaryValue((byte[]) value);479} else {480comparable = ((String) value).toUpperCase(Locale.ENGLISH);481}482return comparable;483}484}485486/**487* Retrieves the number of attribute type/value pairs in this Rdn.488* @return The non-negative number of type/value pairs in this Rdn.489*/490public int size() {491return entries.size();492}493494/**495* Given the value of an attribute, returns a string escaped according496* to the rules specified in497* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>.498* <p>499* For example, if the val is "Sue, Grabbit and Runn", the escaped500* value returned by this method is "Sue\, Grabbit and Runn".501* <p>502* A string value is represented as a String and binary value503* as a byte array.504*505* @param val The non-null object to be escaped.506* @return Escaped string value.507* @throws ClassCastException if val is is not a String or byte array.508*/509public static String escapeValue(Object val) {510return (val instanceof byte[])511? escapeBinaryValue((byte[])val)512: escapeStringValue((String)val);513}514515/*516* Given the value of a string-valued attribute, returns a517* string suitable for inclusion in a DN. This is accomplished by518* using backslash (\) to escape the following characters:519* leading and trailing whitespace520* , = + < > # ; " \521*/522private static final String escapees = ",=+<>#;\"\\";523524private static String escapeStringValue(String val) {525526char[] chars = val.toCharArray();527StringBuilder builder = new StringBuilder(2 * val.length());528529// Find leading and trailing whitespace.530int lead; // index of first char that is not leading whitespace531for (lead = 0; lead < chars.length; lead++) {532if (!isWhitespace(chars[lead])) {533break;534}535}536int trail; // index of last char that is not trailing whitespace537for (trail = chars.length - 1; trail >= 0; trail--) {538if (!isWhitespace(chars[trail])) {539break;540}541}542543for (int i = 0; i < chars.length; i++) {544char c = chars[i];545if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {546builder.append('\\');547}548builder.append(c);549}550return builder.toString();551}552553/*554* Given the value of a binary attribute, returns a string555* suitable for inclusion in a DN (such as "#CEB1DF80").556* TBD: This method should actually generate the ber encoding557* of the binary value558*/559private static String escapeBinaryValue(byte[] val) {560561StringBuilder builder = new StringBuilder(1 + 2 * val.length);562builder.append("#");563564for (int i = 0; i < val.length; i++) {565byte b = val[i];566builder.append(Character.forDigit(0xF & (b >>> 4), 16));567builder.append(Character.forDigit(0xF & b, 16));568}569return builder.toString();570}571572/**573* Given an attribute value string formated according to the rules574* specified in575* <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>,576* returns the unformated value. Escapes and quotes are577* stripped away, and hex-encoded UTF-8 is converted to equivalent578* UTF-16 characters. Returns a string value as a String, and a579* binary value as a byte array.580* <p>581* Legal and illegal values are defined in RFC 2253.582* This method is generous in accepting the values and does not583* catch all illegal values.584* Therefore, passing in an illegal value might not necessarily585* trigger an <tt>IllegalArgumentException</tt>.586*587* @param val The non-null string to be unescaped.588* @return Unescaped value.589* @throws IllegalArgumentException When an Illegal value590* is provided.591*/592public static Object unescapeValue(String val) {593594char[] chars = val.toCharArray();595int beg = 0;596int end = chars.length;597598// Trim off leading and trailing whitespace.599while ((beg < end) && isWhitespace(chars[beg])) {600++beg;601}602603while ((beg < end) && isWhitespace(chars[end - 1])) {604--end;605}606607// Add back the trailing whitespace with a preceding '\'608// (escaped or unescaped) that was taken off in the above609// loop. Whether or not to retain this whitespace is decided below.610if (end != chars.length &&611(beg < end) &&612chars[end - 1] == '\\') {613end++;614}615if (beg >= end) {616return "";617}618619if (chars[beg] == '#') {620// Value is binary (eg: "#CEB1DF80").621return decodeHexPairs(chars, ++beg, end);622}623624// Trim off quotes.625if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {626++beg;627--end;628}629630StringBuilder builder = new StringBuilder(end - beg);631int esc = -1; // index of the last escaped character632633for (int i = beg; i < end; i++) {634if ((chars[i] == '\\') && (i + 1 < end)) {635if (!Character.isLetterOrDigit(chars[i + 1])) {636++i; // skip backslash637builder.append(chars[i]); // snarf escaped char638esc = i;639} else {640641// Convert hex-encoded UTF-8 to 16-bit chars.642byte[] utf8 = getUtf8Octets(chars, i, end);643if (utf8.length > 0) {644try {645builder.append(new String(utf8, "UTF8"));646} catch (java.io.UnsupportedEncodingException e) {647// shouldn't happen648}649i += utf8.length * 3 - 1;650} else { // no utf8 bytes available, invalid DN651652// '/' has no meaning, throw exception653throw new IllegalArgumentException(654"Not a valid attribute string value:" +655val + ",improper usage of backslash");656}657}658} else {659builder.append(chars[i]); // snarf unescaped char660}661}662663// Get rid of the unescaped trailing whitespace with the664// preceding '\' character that was previously added back.665int len = builder.length();666if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) {667builder.setLength(len - 1);668}669return builder.toString();670}671672673/*674* Given an array of chars (with starting and ending indexes into it)675* representing bytes encoded as hex-pairs (such as "CEB1DF80"),676* returns a byte array containing the decoded bytes.677*/678private static byte[] decodeHexPairs(char[] chars, int beg, int end) {679byte[] bytes = new byte[(end - beg) / 2];680for (int i = 0; beg + 1 < end; i++) {681int hi = Character.digit(chars[beg], 16);682int lo = Character.digit(chars[beg + 1], 16);683if (hi < 0 || lo < 0) {684break;685}686bytes[i] = (byte)((hi<<4) + lo);687beg += 2;688}689if (beg != end) {690throw new IllegalArgumentException(691"Illegal attribute value: " + new String(chars));692}693return bytes;694}695696/*697* Given an array of chars (with starting and ending indexes into it),698* finds the largest prefix consisting of hex-encoded UTF-8 octets,699* and returns a byte array containing the corresponding UTF-8 octets.700*701* Hex-encoded UTF-8 octets look like this:702* \03\B1\DF\80703*/704private static byte[] getUtf8Octets(char[] chars, int beg, int end) {705byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room706int len = 0; // index of first unused byte in utf8707708while ((beg + 2 < end) &&709(chars[beg++] == '\\')) {710int hi = Character.digit(chars[beg++], 16);711int lo = Character.digit(chars[beg++], 16);712if (hi < 0 || lo < 0) {713break;714}715utf8[len++] = (byte)((hi<<4) + lo);716}717if (len == utf8.length) {718return utf8;719} else {720byte[] res = new byte[len];721System.arraycopy(utf8, 0, res, 0, len);722return res;723}724}725726/*727* Best guess as to what RFC 2253 means by "whitespace".728*/729private static boolean isWhitespace(char c) {730return (c == ' ' || c == '\r');731}732733/**734* Serializes only the unparsed RDN, for compactness and to avoid735* any implementation dependency.736*737* @serialData The RDN string738*/739private void writeObject(ObjectOutputStream s)740throws java.io.IOException {741s.defaultWriteObject();742s.writeObject(toString());743}744745private void readObject(ObjectInputStream s)746throws IOException, ClassNotFoundException {747s.defaultReadObject();748entries = new ArrayList<>(DEFAULT_SIZE);749String unparsed = (String) s.readObject();750try {751(new Rfc2253Parser(unparsed)).parseRdn(this);752} catch (InvalidNameException e) {753// shouldn't happen754throw new java.io.StreamCorruptedException(755"Invalid name: " + unparsed);756}757}758}759760761