/****************************************************************************** * The MIT License * Copyright (c) 2003 Novell Inc. www.novell.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ // // Novell.Directory.Ldap.Utilclass.DN.cs // // Author: // Sunil Kumar (Sunilk@novell.com) // // (C) 2003 Novell, Inc (http://www.novell.com) // using System; namespace Novell.Directory.Ldap.Utilclass { /// A DN encapsulates a Distinguished Name (an ldap name with context). A DN /// does not need to be fully distinguished, or extend to the Root of a /// directory. It provides methods to get information about the DN and to /// manipulate the DN. /// /// The following are examples of valid DN: /// /// /// Note: Multivalued attributes are all considered to be one /// component and are represented in one RDN (see RDN) /// /// /// /// /// public class DN : System.Object { private void InitBlock() { rdnList = new System.Collections.ArrayList(); } /// Retrieves a list of RDN Objects, or individual names of the DN /// list of RDNs /// virtual public System.Collections.ArrayList RDNs { get { int size = rdnList.Count; System.Collections.ArrayList v = new System.Collections.ArrayList(size); for (int i = 0; i < size; i++) { v.Add(rdnList[i]); } return v; } } /// Returns the Parent of this DN /// Parent DN /// virtual public DN Parent { get { DN parent = new DN(); parent.rdnList = (System.Collections.ArrayList) this.rdnList.Clone(); if (parent.rdnList.Count >= 1) parent.rdnList.Remove(rdnList[0]); //remove first object return parent; } } //parser state identifiers. private const int LOOK_FOR_RDN_ATTR_TYPE = 1; private const int ALPHA_ATTR_TYPE = 2; private const int OID_ATTR_TYPE = 3; private const int LOOK_FOR_RDN_VALUE = 4; private const int QUOTED_RDN_VALUE = 5; private const int HEX_RDN_VALUE = 6; private const int UNQUOTED_RDN_VALUE = 7; /* State transition table: Parsing starts in state 1. State COMMA DIGIT "Oid." ALPHA EQUAL QUOTE SHARP HEX -------------------------------------------------------------------- 1 Err 3 3 2 Err Err Err Err 2 Err Err Err 2 4 Err Err Err 3 Err 3 Err Err 4 Err Err Err 4 Err 7 Err 7 Err 5 6 7 5 1 5 Err 5 Err 1 Err 7 6 1 6 Err Err Err Err Err 6 7 1 7 Err 7 Err Err Err 7 */ private System.Collections.ArrayList rdnList; public DN() { InitBlock(); return ; } /// Constructs a new DN based on the specified string representation of a /// distinguished name. The syntax of the DN must conform to that specified /// in RFC 2253. /// /// /// a string representation of the distinguished name /// /// IllegalArgumentException if the the value of the dnString /// parameter does not adhere to the syntax described in /// RFC 2253 /// public DN(System.String dnString) { InitBlock(); /* the empty string is a valid DN */ if (dnString.Length == 0) return ; char currChar; char nextChar; int currIndex; char[] tokenBuf = new char[dnString.Length]; int tokenIndex; int lastIndex; int valueStart; int state; int trailingSpaceCount = 0; System.String attrType = ""; System.String attrValue = ""; System.String rawValue = ""; int hexDigitCount = 0; RDN currRDN = new RDN(); //indicates whether an OID number has a first digit of ZERO bool firstDigitZero = false; tokenIndex = 0; currIndex = 0; valueStart = 0; state = LOOK_FOR_RDN_ATTR_TYPE; lastIndex = dnString.Length - 1; while (currIndex <= lastIndex) { currChar = dnString[currIndex]; switch (state) { case LOOK_FOR_RDN_ATTR_TYPE: while (currChar == ' ' && (currIndex < lastIndex)) currChar = dnString[++currIndex]; if (isAlpha(currChar)) { if (dnString.Substring(currIndex).StartsWith("oid.") || dnString.Substring(currIndex).StartsWith("OID.")) { //form is "oid.###.##.###... or OID.###.##.###... currIndex += 4; //skip oid. prefix and get to actual oid if (currIndex > lastIndex) throw new System.ArgumentException(dnString); currChar = dnString[currIndex]; if (isDigit(currChar)) { tokenBuf[tokenIndex++] = currChar; state = OID_ATTR_TYPE; } else throw new System.ArgumentException(dnString); } else { tokenBuf[tokenIndex++] = currChar; state = ALPHA_ATTR_TYPE; } } else if (isDigit(currChar)) { --currIndex; state = OID_ATTR_TYPE; } else if (!(System.Char.GetUnicodeCategory(currChar) == System.Globalization.UnicodeCategory.SpaceSeparator)) throw new System.ArgumentException(dnString); break; case ALPHA_ATTR_TYPE: if (isAlpha(currChar) || isDigit(currChar) || (currChar == '-')) tokenBuf[tokenIndex++] = currChar; else { //skip any spaces while ((currChar == ' ') && (currIndex < lastIndex)) currChar = dnString[++currIndex]; if (currChar == '=') { attrType = new System.String(tokenBuf, 0, tokenIndex); tokenIndex = 0; state = LOOK_FOR_RDN_VALUE; } else throw new System.ArgumentException(dnString); } break; case OID_ATTR_TYPE: if (!isDigit(currChar)) throw new System.ArgumentException(dnString); firstDigitZero = (currChar == '0')?true:false; tokenBuf[tokenIndex++] = currChar; currChar = dnString[++currIndex]; if ((isDigit(currChar) && firstDigitZero) || (currChar == '.' && firstDigitZero)) { throw new System.ArgumentException(dnString); } //consume all numbers. while (isDigit(currChar) && (currIndex < lastIndex)) { tokenBuf[tokenIndex++] = currChar; currChar = dnString[++currIndex]; } if (currChar == '.') { tokenBuf[tokenIndex++] = currChar; //The state remains at OID_ATTR_TYPE } else { //skip any spaces while (currChar == ' ' && (currIndex < lastIndex)) currChar = dnString[++currIndex]; if (currChar == '=') { attrType = new System.String(tokenBuf, 0, tokenIndex); tokenIndex = 0; state = LOOK_FOR_RDN_VALUE; } else throw new System.ArgumentException(dnString); } break; case LOOK_FOR_RDN_VALUE: while (currChar == ' ') { if (currIndex < lastIndex) currChar = dnString[++currIndex]; else throw new System.ArgumentException(dnString); } if (currChar == '"') { state = QUOTED_RDN_VALUE; valueStart = currIndex; } else if (currChar == '#') { hexDigitCount = 0; tokenBuf[tokenIndex++] = currChar; valueStart = currIndex; state = HEX_RDN_VALUE; } else { valueStart = currIndex; //check this character again in the UNQUOTED_RDN_VALUE state currIndex--; state = UNQUOTED_RDN_VALUE; } break; case UNQUOTED_RDN_VALUE: if (currChar == '\\') { if (!(currIndex < lastIndex)) throw new System.ArgumentException(dnString); currChar = dnString[++currIndex]; if (isHexDigit(currChar)) { if (!(currIndex < lastIndex)) throw new System.ArgumentException(dnString); nextChar = dnString[++currIndex]; if (isHexDigit(nextChar)) { tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar); trailingSpaceCount = 0; } else throw new System.ArgumentException(dnString); } else if (needsEscape(currChar) || currChar == '#' || currChar == '=' || currChar == ' ') { tokenBuf[tokenIndex++] = currChar; trailingSpaceCount = 0; } else throw new System.ArgumentException(dnString); } else if (currChar == ' ') { trailingSpaceCount++; tokenBuf[tokenIndex++] = currChar; } else if ((currChar == ',') || (currChar == ';') || (currChar == '+')) { attrValue = new System.String(tokenBuf, 0, tokenIndex - trailingSpaceCount); rawValue = dnString.Substring(valueStart, (currIndex - trailingSpaceCount) - (valueStart)); currRDN.add(attrType, attrValue, rawValue); if (currChar != '+') { rdnList.Add(currRDN); currRDN = new RDN(); } trailingSpaceCount = 0; tokenIndex = 0; state = LOOK_FOR_RDN_ATTR_TYPE; } else if (needsEscape(currChar)) { throw new System.ArgumentException(dnString); } else { trailingSpaceCount = 0; tokenBuf[tokenIndex++] = currChar; } break; //end UNQUOTED RDN VALUE case QUOTED_RDN_VALUE: if (currChar == '"') { rawValue = dnString.Substring(valueStart, (currIndex + 1) - (valueStart)); if (currIndex < lastIndex) currChar = dnString[++currIndex]; //skip any spaces while ((currChar == ' ') && (currIndex < lastIndex)) currChar = dnString[++currIndex]; if ((currChar == ',') || (currChar == ';') || (currChar == '+') || (currIndex == lastIndex)) { attrValue = new System.String(tokenBuf, 0, tokenIndex); currRDN.add(attrType, attrValue, rawValue); if (currChar != '+') { rdnList.Add(currRDN); currRDN = new RDN(); } trailingSpaceCount = 0; tokenIndex = 0; state = LOOK_FOR_RDN_ATTR_TYPE; } else throw new System.ArgumentException(dnString); } else if (currChar == '\\') { currChar = dnString[++currIndex]; if (isHexDigit(currChar)) { nextChar = dnString[++currIndex]; if (isHexDigit(nextChar)) { tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar); trailingSpaceCount = 0; } else throw new System.ArgumentException(dnString); } else if (needsEscape(currChar) || currChar == '#' || currChar == '=' || currChar == ' ') { tokenBuf[tokenIndex++] = currChar; trailingSpaceCount = 0; } else throw new System.ArgumentException(dnString); } else tokenBuf[tokenIndex++] = currChar; break; //end QUOTED RDN VALUE case HEX_RDN_VALUE: if ((!isHexDigit(currChar)) || (currIndex > lastIndex)) { //check for odd number of hex digits if ((hexDigitCount % 2) != 0 || hexDigitCount == 0) throw new System.ArgumentException(dnString); else { rawValue = dnString.Substring(valueStart, (currIndex) - (valueStart)); //skip any spaces while ((currChar == ' ') && (currIndex < lastIndex)) currChar = dnString[++currIndex]; if ((currChar == ',') || (currChar == ';') || (currChar == '+') || (currIndex == lastIndex)) { attrValue = new System.String(tokenBuf, 0, tokenIndex); //added by cameron currRDN.add(attrType, attrValue, rawValue); if (currChar != '+') { rdnList.Add(currRDN); currRDN = new RDN(); } tokenIndex = 0; state = LOOK_FOR_RDN_ATTR_TYPE; } else { throw new System.ArgumentException(dnString); } } } else { tokenBuf[tokenIndex++] = currChar; hexDigitCount++; } break; //end HEX RDN VALUE } //end switch currIndex++; } //end while //check ending state if (state == UNQUOTED_RDN_VALUE || (state == HEX_RDN_VALUE && (hexDigitCount % 2) == 0) && hexDigitCount != 0) { attrValue = new System.String(tokenBuf, 0, tokenIndex - trailingSpaceCount); rawValue = dnString.Substring(valueStart, (currIndex - trailingSpaceCount) - (valueStart)); currRDN.add(attrType, attrValue, rawValue); rdnList.Add(currRDN); } else if (state == LOOK_FOR_RDN_VALUE) { //empty value is valid attrValue = ""; rawValue = dnString.Substring(valueStart); currRDN.add(attrType, attrValue, rawValue); rdnList.Add(currRDN); } else { throw new System.ArgumentException(dnString); } } //end DN constructor (string dn) /// Checks a character to see if it is an ascii alphabetic character in /// ranges 65-90 or 97-122. /// /// /// the character to be tested. /// /// true if the character is an ascii alphabetic /// character /// private bool isAlpha(char ch) { if (((ch < 91) && (ch > 64)) || ((ch < 123) && (ch > 96))) //ASCII A-Z return true; else return false; } /// Checks a character to see if it is an ascii digit (0-9) character in /// the ascii value range 48-57. /// /// /// the character to be tested. /// /// true if the character is an ascii alphabetic /// character /// private bool isDigit(char ch) { if ((ch < 58) && (ch > 47)) //ASCII 0-9 return true; else return false; } /// Checks a character to see if it is valid hex digit 0-9, a-f, or /// A-F (ASCII value ranges 48-47, 65-70, 97-102). /// /// /// the character to be tested. /// /// true if the character is a valid hex digit /// private static bool isHexDigit(char ch) { if (((ch < 58) && (ch > 47)) || ((ch < 71) && (ch > 64)) || ((ch < 103) && (ch > 96))) //ASCII A-F return true; else return false; } /// Checks a character to see if it must always be escaped in the /// string representation of a DN. We must tests for space, sharp, and /// equals individually. /// /// /// the character to be tested. /// /// true if the character needs to be escaped in at /// least some instances. /// private bool needsEscape(char ch) { if ((ch == ',') || (ch == '+') || (ch == '\"') || (ch == ';') || (ch == '<') || (ch == '>') || (ch == '\\')) return true; else return false; } /// Converts two valid hex digit characters that form the string /// representation of an ascii character value to the actual ascii /// character. /// /// /// the hex digit for the high order byte. /// /// the hex digit for the low order byte. /// /// the character whose value is represented by the parameters. /// private static char hexToChar(char hex1, char hex0) { int result; if ((hex1 < 58) && (hex1 > 47)) //ASCII 0-9 result = (hex1 - 48) * 16; else if ((hex1 < 71) && (hex1 > 64)) //ASCII a-f result = (hex1 - 55) * 16; else if ((hex1 < 103) && (hex1 > 96)) //ASCII A-F result = (hex1 - 87) * 16; else throw new System.ArgumentException("Not hex digit"); if ((hex0 < 58) && (hex0 > 47)) //ASCII 0-9 result += (hex0 - 48); else if ((hex0 < 71) && (hex0 > 64)) //ASCII a-f result += (hex0 - 55); else if ((hex0 < 103) && (hex0 > 96)) //ASCII A-F result += (hex0 - 87); else throw new System.ArgumentException("Not hex digit"); return (char) result; } /// Creates and returns a string that represents this DN. The string /// follows RFC 2253, which describes String representation of DN's and /// RDN's /// /// /// A DN string. /// public override System.String ToString() { int length = rdnList.Count; System.String dn = ""; if (length < 1) return null; dn = rdnList[0].ToString(); for (int i = 1; i < length; i++) { dn += ("," + rdnList[i].ToString()); } return dn; } /// Compares this DN to the specified DN to determine if they are equal. /// /// /// the DN to compare to /// /// true if the DNs are equal; otherwise /// false /// public System.Collections.ArrayList getrdnList() { return this.rdnList; } public override bool Equals(System.Object toDN) { return Equals((DN) toDN); } public bool Equals(DN toDN) { System.Collections.ArrayList aList=toDN.getrdnList(); int length = aList.Count; if (this.rdnList.Count != length) return false; for (int i = 0; i < length; i++) { if (!((RDN) rdnList[i]).equals((RDN) toDN.getrdnList()[i])) return false; } return true; } /// return a string array of the individual RDNs contained in the DN /// /// /// If true, returns only the values of the /// components, and not the names, e.g. "Babs /// Jensen", "Accounting", "Acme", "us" - instead of /// "cn=Babs Jensen", "ou=Accounting", "o=Acme", and /// "c=us". /// /// String[] containing the rdns in the DN with /// the leftmost rdn in the first element of the array /// /// public virtual System.String[] explodeDN(bool noTypes) { int length = rdnList.Count; System.String[] rdns = new System.String[length]; for (int i = 0; i < length; i++) rdns[i] = ((RDN) rdnList[i]).toString(noTypes); return rdns; } /// Retrieves the count of RDNs, or individule names, in the Distinguished name /// the count of RDN /// public virtual int countRDNs() { return rdnList.Count; } /// Determines if this DN is contained by the DN passed in. For /// example: "cn=admin, ou=marketing, o=corporation" is contained by /// "o=corporation", "ou=marketing, o=corporation", and "ou=marketing" /// but not by "cn=admin" or "cn=admin,ou=marketing,o=corporation" /// Note: For users of Netscape's SDK this method is comparable to contains /// /// /// of a container /// /// true if containerDN contains this DN /// public virtual bool isDescendantOf(DN containerDN) { int i = containerDN.rdnList.Count - 1; //index to an RDN of the ContainerDN int j = this.rdnList.Count - 1; //index to an RDN of the ContainedDN //Search from the end of the DN for an RDN that matches the end RDN of //containerDN. while (!((RDN) this.rdnList[j--]).equals((RDN) containerDN.rdnList[i])) { if (j <= 0) return false; //if the end RDN of containerDN does not have any equal //RDN in rdnList, then containerDN does not contain this DN } i--; //avoid a redundant compare j--; //step backwards to verify that all RDNs in containerDN exist in this DN for (; i >= 0 && j >= 0; i--, j--) { if (!((RDN) this.rdnList[j]).equals((RDN) containerDN.rdnList[i])) return false; } if (j == 0 && i == 0) //the DNs are identical and thus not contained return false; return true; } /// Adds the RDN to the beginning of the current DN. /// an RDN to be added /// public virtual void addRDN(RDN rdn) { rdnList.Insert(0, rdn); } /// Adds the RDN to the beginning of the current DN. /// an RDN to be added /// public virtual void addRDNToFront(RDN rdn) { rdnList.Insert(0, rdn); } /// Adds the RDN to the end of the current DN /// an RDN to be added /// public virtual void addRDNToBack(RDN rdn) { rdnList.Add(rdn); } } //end class DN }