Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

745 lines
22 KiB
C#

/******************************************************************************
* 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
{
/// <summary> 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:
/// <ul>
/// <li>cn=admin,ou=marketing,o=corporation</li>
/// <li>cn=admin,ou=marketing</li>
/// <li>2.5.4.3=admin,ou=marketing</li>
/// <li>oid.2.5.4.3=admin,ou=marketing</li>
/// </ul>
///
/// Note: Multivalued attributes are all considered to be one
/// component and are represented in one RDN (see RDN)
///
///
/// </summary>
/// <seealso cref="RDN">
/// </seealso>
public class DN : System.Object
{
private void InitBlock()
{
rdnList = new System.Collections.ArrayList();
}
/// <summary> Retrieves a list of RDN Objects, or individual names of the DN</summary>
/// <returns> list of RDNs
/// </returns>
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;
}
}
/// <summary> Returns the Parent of this DN</summary>
/// <returns> Parent DN
/// </returns>
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 ;
}
/// <summary> 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.
///
/// </summary>
/// <param name="dnString">a string representation of the distinguished name
/// </param>
/// <exception> IllegalArgumentException if the the value of the dnString
/// parameter does not adhere to the syntax described in
/// RFC 2253
/// </exception>
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)
/// <summary> Checks a character to see if it is an ascii alphabetic character in
/// ranges 65-90 or 97-122.
///
/// </summary>
/// <param name="ch">the character to be tested.
/// </param>
/// <returns> <code>true</code> if the character is an ascii alphabetic
/// character
/// </returns>
private bool isAlpha(char ch)
{
if (((ch < 91) && (ch > 64)) || ((ch < 123) && (ch > 96)))
//ASCII A-Z
return true;
else
return false;
}
/// <summary> Checks a character to see if it is an ascii digit (0-9) character in
/// the ascii value range 48-57.
///
/// </summary>
/// <param name="ch">the character to be tested.
/// </param>
/// <returns> <code>true</code> if the character is an ascii alphabetic
/// character
/// </returns>
private bool isDigit(char ch)
{
if ((ch < 58) && (ch > 47))
//ASCII 0-9
return true;
else
return false;
}
/// <summary> 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).
///
/// </summary>
/// <param name="ch">the character to be tested.
/// </param>
/// <returns> <code>true</code> if the character is a valid hex digit
/// </returns>
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;
}
/// <summary> 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.
///
/// </summary>
/// <param name="ch">the character to be tested.
/// </param>
/// <returns> <code>true</code> if the character needs to be escaped in at
/// least some instances.
/// </returns>
private bool needsEscape(char ch)
{
if ((ch == ',') || (ch == '+') || (ch == '\"') || (ch == ';') || (ch == '<') || (ch == '>') || (ch == '\\'))
return true;
else
return false;
}
/// <summary> Converts two valid hex digit characters that form the string
/// representation of an ascii character value to the actual ascii
/// character.
///
/// </summary>
/// <param name="hex1">the hex digit for the high order byte.
/// </param>
/// <param name="hex0">the hex digit for the low order byte.
/// </param>
/// <returns> the character whose value is represented by the parameters.
/// </returns>
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;
}
/// <summary> Creates and returns a string that represents this DN. The string
/// follows RFC 2253, which describes String representation of DN's and
/// RDN's
///
/// </summary>
/// <returns> A DN string.
/// </returns>
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;
}
/// <summary> Compares this DN to the specified DN to determine if they are equal.
///
/// </summary>
/// <param name="toDN">the DN to compare to
/// </param>
/// <returns> <code>true</code> if the DNs are equal; otherwise
/// <code>false</code>
/// </returns>
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;
}
/// <summary> return a string array of the individual RDNs contained in the DN
///
/// </summary>
/// <param name="noTypes"> 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".
/// </param>
/// <returns> <code>String[]</code> containing the rdns in the DN with
/// the leftmost rdn in the first element of the array
///
/// </returns>
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;
}
/// <summary> Retrieves the count of RDNs, or individule names, in the Distinguished name</summary>
/// <returns> the count of RDN
/// </returns>
public virtual int countRDNs()
{
return rdnList.Count;
}
/// <summary>Determines if this DN is <I>contained</I> 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 <B>not</B> by "cn=admin" or "cn=admin,ou=marketing,o=corporation"
/// Note: For users of Netscape's SDK this method is comparable to contains
///
/// </summary>
/// <param name="containerDN">of a container
/// </param>
/// <returns> true if containerDN contains this DN
/// </returns>
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]))
{
j--;
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;
}
/// <summary> Adds the RDN to the beginning of the current DN.</summary>
/// <param name="rdn">an RDN to be added
/// </param>
public virtual void addRDN(RDN rdn)
{
rdnList.Insert(0, rdn);
}
/// <summary> Adds the RDN to the beginning of the current DN.</summary>
/// <param name="rdn">an RDN to be added
/// </param>
public virtual void addRDNToFront(RDN rdn)
{
rdnList.Insert(0, rdn);
}
/// <summary> Adds the RDN to the end of the current DN</summary>
/// <param name="rdn">an RDN to be added
/// </param>
public virtual void addRDNToBack(RDN rdn)
{
rdnList.Add(rdn);
}
} //end class DN
}