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
);