1 /******************************************************************************
3 * Copyright (c) 2003 Novell Inc. www.novell.com
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the Software), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 *******************************************************************************/
24 // Novell.Directory.Ldap.Utilclass.DN.cs
27 // Sunil Kumar (Sunilk@novell.com)
29 // (C) 2003 Novell, Inc (http://www.novell.com)
34 namespace Novell.Directory.Ldap.Utilclass
37 /// <summary> A DN encapsulates a Distinguished Name (an ldap name with context). A DN
38 /// does not need to be fully distinguished, or extend to the Root of a
39 /// directory. It provides methods to get information about the DN and to
40 /// manipulate the DN.
42 /// The following are examples of valid DN:
44 /// <li>cn=admin,ou=marketing,o=corporation</li>
45 /// <li>cn=admin,ou=marketing</li>
46 /// <li>2.5.4.3=admin,ou=marketing</li>
47 /// <li>oid.2.5.4.3=admin,ou=marketing</li>
50 /// Note: Multivalued attributes are all considered to be one
51 /// component and are represented in one RDN (see RDN)
55 /// <seealso cref="RDN">
58 public class DN : System.Object
60 private void InitBlock()
62 rdnList = new System.Collections.ArrayList();
64 /// <summary> Retrieves a list of RDN Objects, or individual names of the DN</summary>
65 /// <returns> list of RDNs
67 virtual public System.Collections.ArrayList RDNs
71 int size = rdnList.Count;
72 System.Collections.ArrayList v = new System.Collections.ArrayList(size);
73 for (int i = 0; i < size; i++)
81 /// <summary> Returns the Parent of this DN</summary>
82 /// <returns> Parent DN
84 virtual public DN Parent
89 parent.rdnList = (System.Collections.ArrayList) this.rdnList.Clone();
90 if (parent.rdnList.Count >= 1)
91 parent.rdnList.Remove(rdnList[0]); //remove first object
97 //parser state identifiers.
98 private const int LOOK_FOR_RDN_ATTR_TYPE = 1;
99 private const int ALPHA_ATTR_TYPE = 2;
100 private const int OID_ATTR_TYPE = 3;
101 private const int LOOK_FOR_RDN_VALUE = 4;
102 private const int QUOTED_RDN_VALUE = 5;
103 private const int HEX_RDN_VALUE = 6;
104 private const int UNQUOTED_RDN_VALUE = 7;
106 /* State transition table: Parsing starts in state 1.
108 State COMMA DIGIT "Oid." ALPHA EQUAL QUOTE SHARP HEX
109 --------------------------------------------------------------------
110 1 Err 3 3 2 Err Err Err Err
111 2 Err Err Err 2 4 Err Err Err
112 3 Err 3 Err Err 4 Err Err Err
113 4 Err 7 Err 7 Err 5 6 7
114 5 1 5 Err 5 Err 1 Err 7
115 6 1 6 Err Err Err Err Err 6
116 7 1 7 Err 7 Err Err Err 7
121 private System.Collections.ArrayList rdnList;
128 /// <summary> Constructs a new DN based on the specified string representation of a
129 /// distinguished name. The syntax of the DN must conform to that specified
133 /// <param name="dnString">a string representation of the distinguished name
135 /// <exception> IllegalArgumentException if the the value of the dnString
136 /// parameter does not adhere to the syntax described in
139 public DN(System.String dnString)
142 /* the empty string is a valid DN */
143 if (dnString.Length == 0)
149 char[] tokenBuf = new char[dnString.Length];
154 int trailingSpaceCount = 0;
155 System.String attrType = "";
156 System.String attrValue = "";
157 System.String rawValue = "";
158 int hexDigitCount = 0;
159 RDN currRDN = new RDN();
161 //indicates whether an OID number has a first digit of ZERO
162 bool firstDigitZero = false;
167 state = LOOK_FOR_RDN_ATTR_TYPE;
168 lastIndex = dnString.Length - 1;
169 while (currIndex <= lastIndex)
171 currChar = dnString[currIndex];
175 case LOOK_FOR_RDN_ATTR_TYPE:
176 while (currChar == ' ' && (currIndex < lastIndex))
177 currChar = dnString[++currIndex];
178 if (isAlpha(currChar))
180 if (dnString.Substring(currIndex).StartsWith("oid.") || dnString.Substring(currIndex).StartsWith("OID."))
182 //form is "oid.###.##.###... or OID.###.##.###...
183 currIndex += 4; //skip oid. prefix and get to actual oid
184 if (currIndex > lastIndex)
185 throw new System.ArgumentException(dnString);
186 currChar = dnString[currIndex];
187 if (isDigit(currChar))
189 tokenBuf[tokenIndex++] = currChar;
190 state = OID_ATTR_TYPE;
193 throw new System.ArgumentException(dnString);
197 tokenBuf[tokenIndex++] = currChar;
198 state = ALPHA_ATTR_TYPE;
201 else if (isDigit(currChar))
204 state = OID_ATTR_TYPE;
206 else if (!(System.Char.GetUnicodeCategory(currChar) == System.Globalization.UnicodeCategory.SpaceSeparator))
207 throw new System.ArgumentException(dnString);
211 case ALPHA_ATTR_TYPE:
212 if (isAlpha(currChar) || isDigit(currChar) || (currChar == '-'))
213 tokenBuf[tokenIndex++] = currChar;
217 while ((currChar == ' ') && (currIndex < lastIndex))
218 currChar = dnString[++currIndex];
221 attrType = new System.String(tokenBuf, 0, tokenIndex);
223 state = LOOK_FOR_RDN_VALUE;
226 throw new System.ArgumentException(dnString);
232 if (!isDigit(currChar))
233 throw new System.ArgumentException(dnString);
234 firstDigitZero = (currChar == '0')?true:false;
235 tokenBuf[tokenIndex++] = currChar;
236 currChar = dnString[++currIndex];
238 if ((isDigit(currChar) && firstDigitZero) || (currChar == '.' && firstDigitZero))
240 throw new System.ArgumentException(dnString);
243 //consume all numbers.
244 while (isDigit(currChar) && (currIndex < lastIndex))
246 tokenBuf[tokenIndex++] = currChar;
247 currChar = dnString[++currIndex];
251 tokenBuf[tokenIndex++] = currChar;
252 //The state remains at OID_ATTR_TYPE
257 while (currChar == ' ' && (currIndex < lastIndex))
258 currChar = dnString[++currIndex];
261 attrType = new System.String(tokenBuf, 0, tokenIndex);
263 state = LOOK_FOR_RDN_VALUE;
266 throw new System.ArgumentException(dnString);
271 case LOOK_FOR_RDN_VALUE:
272 while (currChar == ' ')
274 if (currIndex < lastIndex)
275 currChar = dnString[++currIndex];
277 throw new System.ArgumentException(dnString);
281 state = QUOTED_RDN_VALUE;
282 valueStart = currIndex;
284 else if (currChar == '#')
287 tokenBuf[tokenIndex++] = currChar;
288 valueStart = currIndex;
289 state = HEX_RDN_VALUE;
293 valueStart = currIndex;
294 //check this character again in the UNQUOTED_RDN_VALUE state
296 state = UNQUOTED_RDN_VALUE;
301 case UNQUOTED_RDN_VALUE:
302 if (currChar == '\\')
304 if (!(currIndex < lastIndex))
305 throw new System.ArgumentException(dnString);
306 currChar = dnString[++currIndex];
307 if (isHexDigit(currChar))
309 if (!(currIndex < lastIndex))
310 throw new System.ArgumentException(dnString);
311 nextChar = dnString[++currIndex];
312 if (isHexDigit(nextChar))
314 tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar);
315 trailingSpaceCount = 0;
318 throw new System.ArgumentException(dnString);
320 else if (needsEscape(currChar) || currChar == '#' || currChar == '=' || currChar == ' ')
322 tokenBuf[tokenIndex++] = currChar;
323 trailingSpaceCount = 0;
326 throw new System.ArgumentException(dnString);
328 else if (currChar == ' ')
330 trailingSpaceCount++;
331 tokenBuf[tokenIndex++] = currChar;
333 else if ((currChar == ',') || (currChar == ';') || (currChar == '+'))
335 attrValue = new System.String(tokenBuf, 0, tokenIndex - trailingSpaceCount);
336 rawValue = dnString.Substring(valueStart, (currIndex - trailingSpaceCount) - (valueStart));
338 currRDN.add(attrType, attrValue, rawValue);
341 rdnList.Add(currRDN);
345 trailingSpaceCount = 0;
347 state = LOOK_FOR_RDN_ATTR_TYPE;
349 else if (needsEscape(currChar))
351 throw new System.ArgumentException(dnString);
355 trailingSpaceCount = 0;
356 tokenBuf[tokenIndex++] = currChar;
358 break; //end UNQUOTED RDN VALUE
361 case QUOTED_RDN_VALUE:
364 rawValue = dnString.Substring(valueStart, (currIndex + 1) - (valueStart));
365 if (currIndex < lastIndex)
366 currChar = dnString[++currIndex];
368 while ((currChar == ' ') && (currIndex < lastIndex))
369 currChar = dnString[++currIndex];
370 if ((currChar == ',') || (currChar == ';') || (currChar == '+') || (currIndex == lastIndex))
372 attrValue = new System.String(tokenBuf, 0, tokenIndex);
374 currRDN.add(attrType, attrValue, rawValue);
377 rdnList.Add(currRDN);
380 trailingSpaceCount = 0;
382 state = LOOK_FOR_RDN_ATTR_TYPE;
385 throw new System.ArgumentException(dnString);
387 else if (currChar == '\\')
389 currChar = dnString[++currIndex];
390 if (isHexDigit(currChar))
392 nextChar = dnString[++currIndex];
393 if (isHexDigit(nextChar))
395 tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar);
396 trailingSpaceCount = 0;
399 throw new System.ArgumentException(dnString);
401 else if (needsEscape(currChar) || currChar == '#' || currChar == '=' || currChar == ' ')
403 tokenBuf[tokenIndex++] = currChar;
404 trailingSpaceCount = 0;
407 throw new System.ArgumentException(dnString);
410 tokenBuf[tokenIndex++] = currChar;
411 break; //end QUOTED RDN VALUE
415 if ((!isHexDigit(currChar)) || (currIndex > lastIndex))
417 //check for odd number of hex digits
418 if ((hexDigitCount % 2) != 0 || hexDigitCount == 0)
419 throw new System.ArgumentException(dnString);
422 rawValue = dnString.Substring(valueStart, (currIndex) - (valueStart));
424 while ((currChar == ' ') && (currIndex < lastIndex))
425 currChar = dnString[++currIndex];
426 if ((currChar == ',') || (currChar == ';') || (currChar == '+') || (currIndex == lastIndex))
428 attrValue = new System.String(tokenBuf, 0, tokenIndex);
431 currRDN.add(attrType, attrValue, rawValue);
434 rdnList.Add(currRDN);
438 state = LOOK_FOR_RDN_ATTR_TYPE;
442 throw new System.ArgumentException(dnString);
448 tokenBuf[tokenIndex++] = currChar;
451 break; //end HEX RDN VALUE
457 if (state == UNQUOTED_RDN_VALUE || (state == HEX_RDN_VALUE && (hexDigitCount % 2) == 0) && hexDigitCount != 0)
459 attrValue = new System.String(tokenBuf, 0, tokenIndex - trailingSpaceCount);
460 rawValue = dnString.Substring(valueStart, (currIndex - trailingSpaceCount) - (valueStart));
461 currRDN.add(attrType, attrValue, rawValue);
462 rdnList.Add(currRDN);
464 else if (state == LOOK_FOR_RDN_VALUE)
466 //empty value is valid
468 rawValue = dnString.Substring(valueStart);
469 currRDN.add(attrType, attrValue, rawValue);
470 rdnList.Add(currRDN);
474 throw new System.ArgumentException(dnString);
476 } //end DN constructor (string dn)
479 /// <summary> Checks a character to see if it is an ascii alphabetic character in
480 /// ranges 65-90 or 97-122.
483 /// <param name="ch">the character to be tested.
485 /// <returns> <code>true</code> if the character is an ascii alphabetic
488 private bool isAlpha(char ch)
490 if (((ch < 91) && (ch > 64)) || ((ch < 123) && (ch > 96)))
498 /// <summary> Checks a character to see if it is an ascii digit (0-9) character in
499 /// the ascii value range 48-57.
502 /// <param name="ch">the character to be tested.
504 /// <returns> <code>true</code> if the character is an ascii alphabetic
507 private bool isDigit(char ch)
509 if ((ch < 58) && (ch > 47))
516 /// <summary> Checks a character to see if it is valid hex digit 0-9, a-f, or
517 /// A-F (ASCII value ranges 48-47, 65-70, 97-102).
520 /// <param name="ch">the character to be tested.
522 /// <returns> <code>true</code> if the character is a valid hex digit
525 private static bool isHexDigit(char ch)
527 if (((ch < 58) && (ch > 47)) || ((ch < 71) && (ch > 64)) || ((ch < 103) && (ch > 96)))
534 /// <summary> Checks a character to see if it must always be escaped in the
535 /// string representation of a DN. We must tests for space, sharp, and
536 /// equals individually.
539 /// <param name="ch">the character to be tested.
541 /// <returns> <code>true</code> if the character needs to be escaped in at
542 /// least some instances.
544 private bool needsEscape(char ch)
546 if ((ch == ',') || (ch == '+') || (ch == '\"') || (ch == ';') || (ch == '<') || (ch == '>') || (ch == '\\'))
552 /// <summary> Converts two valid hex digit characters that form the string
553 /// representation of an ascii character value to the actual ascii
557 /// <param name="hex1">the hex digit for the high order byte.
559 /// <param name="hex0">the hex digit for the low order byte.
561 /// <returns> the character whose value is represented by the parameters.
564 private static char hexToChar(char hex1, char hex0)
568 if ((hex1 < 58) && (hex1 > 47))
570 result = (hex1 - 48) * 16;
571 else if ((hex1 < 71) && (hex1 > 64))
573 result = (hex1 - 55) * 16;
574 else if ((hex1 < 103) && (hex1 > 96))
576 result = (hex1 - 87) * 16;
578 throw new System.ArgumentException("Not hex digit");
580 if ((hex0 < 58) && (hex0 > 47))
582 result += (hex0 - 48);
583 else if ((hex0 < 71) && (hex0 > 64))
585 result += (hex0 - 55);
586 else if ((hex0 < 103) && (hex0 > 96))
588 result += (hex0 - 87);
590 throw new System.ArgumentException("Not hex digit");
592 return (char) result;
595 /// <summary> Creates and returns a string that represents this DN. The string
596 /// follows RFC 2253, which describes String representation of DN's and
600 /// <returns> A DN string.
602 public override System.String ToString()
604 int length = rdnList.Count;
605 System.String dn = "";
608 dn = rdnList[0].ToString();
609 for (int i = 1; i < length; i++)
611 dn += ("," + rdnList[i].ToString());
617 /// <summary> Compares this DN to the specified DN to determine if they are equal.
620 /// <param name="toDN">the DN to compare to
622 /// <returns> <code>true</code> if the DNs are equal; otherwise
623 /// <code>false</code>
626 public System.Collections.ArrayList getrdnList()
630 public override bool Equals(System.Object toDN)
632 return Equals((DN) toDN);
634 public bool Equals(DN toDN)
636 System.Collections.ArrayList aList=toDN.getrdnList();
637 int length = aList.Count;
639 if (this.rdnList.Count != length)
642 for (int i = 0; i < length; i++)
644 if (!((RDN) rdnList[i]).equals((RDN) toDN.getrdnList()[i]))
650 /// <summary> return a string array of the individual RDNs contained in the DN
653 /// <param name="noTypes"> If true, returns only the values of the
654 /// components, and not the names, e.g. "Babs
655 /// Jensen", "Accounting", "Acme", "us" - instead of
656 /// "cn=Babs Jensen", "ou=Accounting", "o=Acme", and
659 /// <returns> <code>String[]</code> containing the rdns in the DN with
660 /// the leftmost rdn in the first element of the array
663 public virtual System.String[] explodeDN(bool noTypes)
665 int length = rdnList.Count;
666 System.String[] rdns = new System.String[length];
667 for (int i = 0; i < length; i++)
668 rdns[i] = ((RDN) rdnList[i]).toString(noTypes);
672 /// <summary> Retrieves the count of RDNs, or individule names, in the Distinguished name</summary>
673 /// <returns> the count of RDN
675 public virtual int countRDNs()
677 return rdnList.Count;
680 /// <summary>Determines if this DN is <I>contained</I> by the DN passed in. For
681 /// example: "cn=admin, ou=marketing, o=corporation" is contained by
682 /// "o=corporation", "ou=marketing, o=corporation", and "ou=marketing"
683 /// but <B>not</B> by "cn=admin" or "cn=admin,ou=marketing,o=corporation"
684 /// Note: For users of Netscape's SDK this method is comparable to contains
687 /// <param name="containerDN">of a container
689 /// <returns> true if containerDN contains this DN
691 public virtual bool isDescendantOf(DN containerDN)
693 int i = containerDN.rdnList.Count - 1; //index to an RDN of the ContainerDN
694 int j = this.rdnList.Count - 1; //index to an RDN of the ContainedDN
695 //Search from the end of the DN for an RDN that matches the end RDN of
697 while (!((RDN) this.rdnList[j--]).equals((RDN) containerDN.rdnList[i]))
701 //if the end RDN of containerDN does not have any equal
702 //RDN in rdnList, then containerDN does not contain this DN
704 i--; //avoid a redundant compare
706 //step backwards to verify that all RDNs in containerDN exist in this DN
707 for (; i >= 0 && j >= 0; i--, j--)
709 if (!((RDN) this.rdnList[j]).equals((RDN) containerDN.rdnList[i]))
712 if (j == 0 && i == 0)
713 //the DNs are identical and thus not contained
719 /// <summary> Adds the RDN to the beginning of the current DN.</summary>
720 /// <param name="rdn">an RDN to be added
722 public virtual void addRDN(RDN rdn)
724 rdnList.Insert(0, rdn);
727 /// <summary> Adds the RDN to the beginning of the current DN.</summary>
728 /// <param name="rdn">an RDN to be added
730 public virtual void addRDNToFront(RDN rdn)
732 rdnList.Insert(0, rdn);
735 /// <summary> Adds the RDN to the end of the current DN</summary>
736 /// <param name="rdn">an RDN to be added
738 public virtual void addRDNToBack(RDN rdn)