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.LdapUrl.cs
27 // Sunil Kumar (Sunilk@novell.com)
29 // (C) 2003 Novell, Inc (http://www.novell.com)
33 using ArrayEnumeration = Novell.Directory.Ldap.Utilclass.ArrayEnumeration;
35 namespace Novell.Directory.Ldap
39 /// Encapsulates parameters of an Ldap URL query as defined in RFC2255.
41 /// An LdapUrl object can be passed to LdapConnection.search to retrieve
45 /// <seealso cref="LdapConnection.Search">
47 public class LdapUrl : System.ICloneable
49 private void InitBlock()
51 scope = DEFAULT_SCOPE;
53 /// <summary> Returns an array of attribute names specified in the URL.
56 /// <returns> An array of attribute names in the URL.
58 virtual public System.String[] AttributeArray
66 /// <summary> Returns an enumerator for the attribute names specified in the URL.
69 /// <returns> An enumeration of attribute names.
71 virtual public System.Collections.IEnumerator Attributes
75 return new ArrayEnumeration(attrs);
79 /// <summary> Returns any Ldap URL extensions specified, or null if none are
80 /// specified. Each extension is a type=value expression. The =value part
81 /// MAY be omitted. The expression MAY be prefixed with '!' if it is
82 /// mandatory for evaluation of the URL.
85 /// <returns> string array of extensions.
87 virtual public System.String[] Extensions
95 /// <summary> Returns the search filter or <code>null</code> if none was specified.
98 /// <returns> The search filter.
100 virtual public System.String Filter
108 /// <summary> Returns the name of the Ldap server in the URL.
111 /// <returns> The host name specified in the URL.
113 virtual public System.String Host
121 /// <summary> Returns the port number of the Ldap server in the URL.
124 /// <returns> The port number in the URL.
126 virtual public int Port
132 return LdapConnection.DEFAULT_PORT;
138 /// <summary> Returns the depth of search. It returns one of the following from
139 /// LdapConnection: SCOPE_BASE, SCOPE_ONE, or SCOPE_SUB.
142 /// <returns> The search scope.
144 virtual public int Scope
152 /// <summary> Returns true if the URL is of the type ldaps (Ldap over SSL, a predecessor
156 /// <returns> whether this is a secure Ldap url or not.
158 virtual public bool Secure
166 private static readonly int DEFAULT_SCOPE = LdapConnection.SCOPE_BASE;
168 // Broken out parts of the URL
169 private bool secure = false; // URL scheme ldap/ldaps
170 private bool ipV6 = false; // TCP/IP V6
171 private System.String host = null; // Host
172 private int port = 0; // Port
173 private System.String dn = null; // Base DN
174 private System.String[] attrs = null; // Attributes
175 private System.String filter = null; // Filter
176 private int scope; // Scope
177 private System.String[] extensions = null; // Extensions
179 /// <summary> Constructs a URL object with the specified string as the URL.
182 /// <param name="url"> An Ldap URL string, e.g.
183 /// "ldap://ldap.example.com:80/dc=example,dc=com?cn,
184 /// sn?sub?(objectclass=inetOrgPerson)".
187 /// <exception> MalformedURLException The specified URL cannot be parsed.
189 public LdapUrl(System.String url)
197 /// <summary> Constructs a URL object with the specified host, port, and DN.
199 /// This form is used to create URL references to a particular object
200 /// in the directory.
203 /// <param name="host"> Host identifier of Ldap server, or null for
207 /// <param name="port"> The port number for Ldap server (use
208 /// LdapConnection.DEFAULT_PORT for default port).
211 /// <param name="dn"> Distinguished name of the base object of the search.
214 public LdapUrl(System.String host, int port, System.String dn)
223 /// <summary> Constructs an Ldap URL with all fields explicitly assigned, to
224 /// specify an Ldap search operation.
227 /// <param name="host"> Host identifier of Ldap server, or null for
231 /// <param name="port"> The port number for Ldap server (use
232 /// LdapConnection.DEFAULT_PORT for default port).
235 /// <param name="dn"> Distinguished name of the base object of the search.
239 /// <param name="attrNames">Names or OIDs of attributes to retrieve. Passing a
240 /// null array signifies that all user attributes are to be
241 /// retrieved. Passing a value of "*" allows you to specify
242 /// that all user attributes as well as any specified
243 /// operational attributes are to be retrieved.
247 /// <param name="scope"> Depth of search (in DN namespace). Use one of
248 /// SCOPE_BASE, SCOPE_ONE, SCOPE_SUB from LdapConnection.
252 /// <param name="filter"> The search filter specifying the search criteria.
256 /// <param name="extensions"> Extensions provide a mechanism to extend the
257 /// functionality of Ldap URLs. Currently no
258 /// Ldap URL extensions are defined. Each extension
259 /// specification is a type=value expression, and may
260 /// be <code>null</code> or empty. The =value part may be
261 /// omitted. The expression may be prefixed with '!' if it
262 /// is mandatory for the evaluation of the URL.
264 public LdapUrl(System.String host, int port, System.String dn, System.String[] attrNames, int scope, System.String filter, System.String[] extensions)
270 this.attrs = new System.String[attrNames.Length];
271 attrNames.CopyTo(this.attrs, 0);
273 this.filter = filter;
274 this.extensions = new System.String[extensions.Length];
275 extensions.CopyTo(this.extensions, 0);
279 /// <summary> Constructs an Ldap URL with all fields explicitly assigned, including
280 /// isSecure, to specify an Ldap search operation.
283 /// <param name="host"> Host identifier of Ldap server, or null for
288 /// <param name="port"> The port number for Ldap server (use
289 /// LdapConnection.DEFAULT_PORT for default port).
293 /// <param name="dn"> Distinguished name of the base object of the search.
297 /// <param name="attrNames">Names or OIDs of attributes to retrieve. Passing a
298 /// null array signifies that all user attributes are to be
299 /// retrieved. Passing a value of "*" allows you to specify
300 /// that all user attributes as well as any specified
301 /// operational attributes are to be retrieved.
305 /// <param name="scope"> Depth of search (in DN namespace). Use one of
306 /// SCOPE_BASE, SCOPE_ONE, SCOPE_SUB from LdapConnection.
310 /// <param name="filter"> The search filter specifying the search criteria.
311 /// from LdapConnection: SCOPE_BASE, SCOPE_ONE, SCOPE_SUB.
315 /// <param name="extensions"> Extensions provide a mechanism to extend the
316 /// functionality of Ldap URLs. Currently no
317 /// Ldap URL extensions are defined. Each extension
318 /// specification is a type=value expression, and may
319 /// be <code>null</code> or empty. The =value part may be
320 /// omitted. The expression may be prefixed with '!' if it
321 /// is mandatory for the evaluation of the URL.
325 /// <param name="secure"> If true creates an Ldap URL of the ldaps type
327 public LdapUrl(System.String host, int port, System.String dn, System.String[] attrNames, int scope, System.String filter, System.String[] extensions, bool secure)
333 this.attrs = attrNames;
335 this.filter = filter;
336 this.extensions = new System.String[extensions.Length];
337 extensions.CopyTo(this.extensions, 0);
338 this.secure = secure;
342 /// <summary> Returns a clone of this URL object.
345 /// <returns> clone of this URL object.
347 public System.Object Clone()
351 return base.MemberwiseClone();
353 catch (System.Exception ce)
355 throw new System.SystemException("Internal error, cannot create clone");
359 /// <summary> Decodes a URL-encoded string.
361 /// Any occurences of %HH are decoded to the hex value represented.
362 /// However, this method does NOT decode "+" into " ".
365 /// <param name="URLEncoded"> String to decode.
368 /// <returns> The decoded string.
371 /// <exception> MalformedURLException The URL could not be parsed.
373 public static System.String decode(System.String URLEncoded)
380 fieldStart = URLEncoded.IndexOf("%", searchStart);
381 // Return now if no encoded data
387 // Decode the %HH value and copy to new string buffer
388 int fieldEnd = 0; // end of previous field
389 int dataLen = URLEncoded.Length;
391 System.Text.StringBuilder decoded = new System.Text.StringBuilder(dataLen);
395 if (fieldStart > (dataLen - 3))
397 throw new System.UriFormatException("LdapUrl.decode: must be two hex characters following escape character '%'");
400 fieldStart = dataLen;
401 // Copy to string buffer from end of last field to start of next
402 decoded.Append(URLEncoded.Substring(fieldEnd, (fieldStart) - (fieldEnd)));
404 if (fieldStart >= dataLen)
406 fieldEnd = fieldStart + 2;
409 decoded.Append((char) System.Convert.ToInt32(URLEncoded.Substring(fieldStart, (fieldEnd) - (fieldStart)), 16));
411 catch (System.FormatException ex)
413 throw new System.UriFormatException("LdapUrl.decode: error converting hex characters to integer \"" + ex.Message + "\"");
415 searchStart = fieldEnd;
416 if (searchStart == dataLen)
418 fieldStart = URLEncoded.IndexOf("%", searchStart);
421 return (decoded.ToString());
424 /// <summary> Encodes an arbitrary string using the URL encoding rules.
426 /// Any illegal characters are encoded as %HH.
429 /// <param name="toEncode"> The string to encode.
432 /// <returns> The URL-encoded string.
434 /// Comment: An illegal character consists of any non graphical US-ASCII character, Unsafe, or reserved characters.
436 public static System.String encode(System.String toEncode)
438 System.Text.StringBuilder buffer = new System.Text.StringBuilder(toEncode.Length); //empty but initial capicity of 'length'
441 for (int i = 0; i < toEncode.Length; i++)
443 currChar = toEncode[i];
444 if ((((int) currChar <= 0x1F) || ((int) currChar == 0x7F) || (((int) currChar >= 0x80) && ((int) currChar <= 0xFF))) || ((currChar == '<') || (currChar == '>') || (currChar == '\"') || (currChar == '#') || (currChar == '%') || (currChar == '{') || (currChar == '}') || (currChar == '|') || (currChar == '\\') || (currChar == '^') || (currChar == '~') || (currChar == '[') || (currChar == '\'')) || ((currChar == ';') || (currChar == '/') || (currChar == '?') || (currChar == ':') || (currChar == '@') || (currChar == '=') || (currChar == '&')))
446 temp = System.Convert.ToString(currChar, 16);
447 if (temp.Length == 1)
448 buffer.Append("%0" + temp);
449 //if(temp.length()==2) this can only be two or one digit long.
451 buffer.Append("%" + System.Convert.ToString(currChar, 16));
454 buffer.Append(currChar);
456 return buffer.ToString();
459 /// <summary> Returns the base distinguished name encapsulated in the URL.
462 /// <returns> The base distinguished name specified in the URL, or null if none.
464 public virtual System.String getDN()
469 /// <summary> Sets the base distinguished name encapsulated in the URL.</summary>
471 internal virtual void setDN(System.String dn)
477 /// <summary> Returns a valid string representation of this Ldap URL.
480 /// <returns> The string representation of the Ldap URL.
482 public override System.String ToString()
484 System.Text.StringBuilder url = new System.Text.StringBuilder(256);
488 url.Append("ldaps://");
492 url.Append("ldap://");
497 url.Append("[" + host + "]");
504 // Port not specified
507 url.Append(":" + port);
510 if (((System.Object) dn == null) && (attrs == null) && (scope == DEFAULT_SCOPE) && ((System.Object) filter == null) && (extensions == null))
512 return url.ToString();
517 if ((System.Object) dn != null)
522 if ((attrs == null) && (scope == DEFAULT_SCOPE) && ((System.Object) filter == null) && (extensions == null))
524 return url.ToString();
531 //should we check also for attrs != "*"
532 for (int i = 0; i < attrs.Length; i++)
534 url.Append(attrs[i]);
535 if (i < (attrs.Length - 1))
542 if ((scope == DEFAULT_SCOPE) && ((System.Object) filter == null) && (extensions == null))
544 return url.ToString();
549 if (scope != DEFAULT_SCOPE)
551 if (scope == LdapConnection.SCOPE_ONE)
561 if (((System.Object) filter == null) && (extensions == null))
563 return url.ToString();
567 if ((System.Object) filter == null)
573 url.Append("?" + Filter);
576 if (extensions == null)
578 return url.ToString();
583 if (extensions != null)
585 for (int i = 0; i < extensions.Length; i++)
587 url.Append(extensions[i]);
588 if (i < (extensions.Length - 1))
594 return url.ToString();
597 private System.String[] parseList(System.String listStr, char delimiter, int listStart, int listEnd)
600 System.String[] list;
601 // Check for and empty string
602 if ((listEnd - listStart) < 1)
606 // First count how many items are specified
607 int itemStart = listStart;
610 while (itemStart > 0)
612 // itemStart == 0 if no delimiter found
614 itemEnd = listStr.IndexOf((System.Char) delimiter, itemStart);
615 if ((itemEnd > 0) && (itemEnd < listEnd))
617 itemStart = itemEnd + 1;
624 // Now fill in the array with the attributes
625 itemStart = listStart;
626 list = new System.String[itemCount];
628 while (itemStart > 0)
630 itemEnd = listStr.IndexOf((System.Char) delimiter, itemStart);
631 if (itemStart <= listEnd)
635 if (itemEnd > listEnd)
637 list[itemCount] = listStr.Substring(itemStart, (itemEnd) - (itemStart));
638 itemStart = itemEnd + 1;
650 private void parseURL(System.String url)
653 int scanEnd = url.Length;
655 if ((System.Object) url == null)
656 throw new System.UriFormatException("LdapUrl: URL cannot be null");
658 // Check if URL is enclosed by < & >
659 if (url[scanStart] == '<')
661 if (url[scanEnd - 1] != '>')
662 throw new System.UriFormatException("LdapUrl: URL bad enclosure");
667 // Determine the URL scheme and set appropriate default port
668 if (url.Substring(scanStart, (scanStart + 4) - (scanStart)).ToUpper().Equals("URL:".ToUpper()))
672 if (url.Substring(scanStart, (scanStart + 7) - (scanStart)).ToUpper().Equals("ldap://".ToUpper()))
675 port = LdapConnection.DEFAULT_PORT;
677 else if (url.Substring(scanStart, (scanStart + 8) - (scanStart)).ToUpper().Equals("ldaps://".ToUpper()))
681 port = LdapConnection.DEFAULT_SSL_PORT;
685 throw new System.UriFormatException("LdapUrl: URL scheme is not ldap");
688 // Find where host:port ends and dn begins
689 int dnStart = url.IndexOf("/", scanStart);
690 int hostPortEnd = scanEnd;
695 * Kludge. check for ldap://111.222.333.444:389??cn=abc,o=company
697 * Check for broken Novell referral format. The dn is in
698 * the scope position, but the required slash is missing.
699 * This is illegal syntax but we need to account for it.
700 * Fortunately it can't be confused with anything real.
702 dnStart = url.IndexOf("?", scanStart);
705 if (url[dnStart + 1] == '?')
707 hostPortEnd = dnStart;
719 hostPortEnd = dnStart;
721 // Check for IPV6 "[ipaddress]:port"
723 int hostEnd = hostPortEnd;
724 if (url[scanStart] == '[')
726 hostEnd = url.IndexOf((System.Char) ']', scanStart + 1);
727 if ((hostEnd >= hostPortEnd) || (hostEnd == - 1))
729 throw new System.UriFormatException("LdapUrl: \"]\" is missing on IPV6 host name");
731 // Get host w/o the [ & ]
732 host = url.Substring(scanStart + 1, (hostEnd) - (scanStart + 1));
733 portStart = url.IndexOf(":", hostEnd);
734 if ((portStart < hostPortEnd) && (portStart != - 1))
737 port = System.Int32.Parse(url.Substring(portStart + 1, (hostPortEnd) - (portStart + 1)));
745 portStart = url.IndexOf(":", scanStart);
746 // Isolate the host and port
747 if ((portStart < 0) || (portStart > hostPortEnd))
749 // no port is specified, we keep the default
750 host = url.Substring(scanStart, (hostPortEnd) - (scanStart));
754 // port specified in URL
755 host = url.Substring(scanStart, (portStart) - (scanStart));
756 port = System.Int32.Parse(url.Substring(portStart + 1, (hostPortEnd) - (portStart + 1)));
760 scanStart = hostPortEnd + 1;
761 if ((scanStart >= scanEnd) || (dnStart < 0))
764 // Parse out the base dn
765 scanStart = dnStart + 1;
767 int attrsStart = url.IndexOf((System.Char) '?', scanStart);
770 dn = url.Substring(scanStart, (scanEnd) - (scanStart));
774 dn = url.Substring(scanStart, (attrsStart) - (scanStart));
777 scanStart = attrsStart + 1;
778 // Wierd novell syntax can have nothing beyond the dn
779 if ((scanStart >= scanEnd) || (attrsStart < 0) || novell)
782 // Parse out the attributes
783 int scopeStart = url.IndexOf((System.Char) '?', scanStart);
785 scopeStart = scanEnd - 1;
786 attrs = parseList(url, ',', attrsStart + 1, scopeStart);
788 scanStart = scopeStart + 1;
789 if (scanStart >= scanEnd)
792 // Parse out the scope
793 int filterStart = url.IndexOf((System.Char) '?', scanStart);
794 System.String scopeStr;
797 scopeStr = url.Substring(scanStart, (scanEnd) - (scanStart));
801 scopeStr = url.Substring(scanStart, (filterStart) - (scanStart));
803 if (scopeStr.ToUpper().Equals("".ToUpper()))
805 scope = LdapConnection.SCOPE_BASE;
807 else if (scopeStr.ToUpper().Equals("base".ToUpper()))
809 scope = LdapConnection.SCOPE_BASE;
811 else if (scopeStr.ToUpper().Equals("one".ToUpper()))
813 scope = LdapConnection.SCOPE_ONE;
815 else if (scopeStr.ToUpper().Equals("sub".ToUpper()))
817 scope = LdapConnection.SCOPE_SUB;
821 throw new System.UriFormatException("LdapUrl: URL invalid scope");
825 scanStart = filterStart + 1;
826 if ((scanStart >= scanEnd) || (filterStart < 0))
829 // Parse out the filter
830 scanStart = filterStart + 1;
832 System.String filterStr;
833 int extStart = url.IndexOf((System.Char) '?', scanStart);
836 filterStr = url.Substring(scanStart, (scanEnd) - (scanStart));
840 filterStr = url.Substring(scanStart, (extStart) - (scanStart));
843 if (!filterStr.Equals(""))
845 filter = filterStr; // Only modify if not the default filter
849 scanStart = extStart + 1;
850 if ((scanStart >= scanEnd) || (extStart < 0))
853 // Parse out the extensions
854 int end = url.IndexOf((System.Char) '?', scanStart);
856 throw new System.UriFormatException("LdapUrl: URL has too many ? fields");
857 extensions = parseList(url, ',', scanStart, scanEnd);