(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / Novell.Directory.Ldap / Novell.Directory.Ldap / LdapUrl.cs
blob3ca98b21892c57f5acbc3af0d12e7c14e3097dd0
1 /******************************************************************************
2 * The MIT License
3 * Copyright (c) 2003 Novell Inc. www.novell.com
4 *
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
21 * SOFTWARE.
22 *******************************************************************************/
24 // Novell.Directory.Ldap.LdapUrl.cs
26 // Author:
27 // Sunil Kumar (Sunilk@novell.com)
29 // (C) 2003 Novell, Inc (http://www.novell.com)
32 using System;
33 using ArrayEnumeration = Novell.Directory.Ldap.Utilclass.ArrayEnumeration;
35 namespace Novell.Directory.Ldap
38 /// <summary>
39 /// Encapsulates parameters of an Ldap URL query as defined in RFC2255.
40 ///
41 /// An LdapUrl object can be passed to LdapConnection.search to retrieve
42 /// search results.
43 ///
44 /// </summary>
45 /// <seealso cref="LdapConnection.Search">
46 /// </seealso>
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.
54 ///
55 /// </summary>
56 /// <returns> An array of attribute names in the URL.
57 /// </returns>
58 virtual public System.String[] AttributeArray
60 get
62 return attrs;
66 /// <summary> Returns an enumerator for the attribute names specified in the URL.
67 ///
68 /// </summary>
69 /// <returns> An enumeration of attribute names.
70 /// </returns>
71 virtual public System.Collections.IEnumerator Attributes
73 get
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.
83 ///
84 /// </summary>
85 /// <returns> string array of extensions.
86 /// </returns>
87 virtual public System.String[] Extensions
89 get
91 return extensions;
95 /// <summary> Returns the search filter or <code>null</code> if none was specified.
96 ///
97 /// </summary>
98 /// <returns> The search filter.
99 /// </returns>
100 virtual public System.String Filter
104 return filter;
108 /// <summary> Returns the name of the Ldap server in the URL.
109 ///
110 /// </summary>
111 /// <returns> The host name specified in the URL.
112 /// </returns>
113 virtual public System.String Host
117 return host;
121 /// <summary> Returns the port number of the Ldap server in the URL.
122 ///
123 /// </summary>
124 /// <returns> The port number in the URL.
125 /// </returns>
126 virtual public int Port
130 if (port == 0)
132 return LdapConnection.DEFAULT_PORT;
134 return port;
138 /// <summary> Returns the depth of search. It returns one of the following from
139 /// LdapConnection: SCOPE_BASE, SCOPE_ONE, or SCOPE_SUB.
140 ///
141 /// </summary>
142 /// <returns> The search scope.
143 /// </returns>
144 virtual public int Scope
148 return scope;
152 /// <summary> Returns true if the URL is of the type ldaps (Ldap over SSL, a predecessor
153 /// to startTls)
154 ///
155 /// </summary>
156 /// <returns> whether this is a secure Ldap url or not.
157 /// </returns>
158 virtual public bool Secure
162 return 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.
180 ///
181 /// </summary>
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)".
185 ///
186 /// </param>
187 /// <exception> MalformedURLException The specified URL cannot be parsed.
188 /// </exception>
189 public LdapUrl(System.String url)
191 InitBlock();
192 parseURL(url);
193 return ;
197 /// <summary> Constructs a URL object with the specified host, port, and DN.
198 ///
199 /// This form is used to create URL references to a particular object
200 /// in the directory.
201 ///
202 /// </summary>
203 /// <param name="host"> Host identifier of Ldap server, or null for
204 /// "localhost".
205 ///
206 /// </param>
207 /// <param name="port"> The port number for Ldap server (use
208 /// LdapConnection.DEFAULT_PORT for default port).
209 ///
210 /// </param>
211 /// <param name="dn"> Distinguished name of the base object of the search.
212 ///
213 /// </param>
214 public LdapUrl(System.String host, int port, System.String dn)
216 InitBlock();
217 this.host = host;
218 this.port = port;
219 this.dn = dn;
220 return ;
223 /// <summary> Constructs an Ldap URL with all fields explicitly assigned, to
224 /// specify an Ldap search operation.
225 ///
226 /// </summary>
227 /// <param name="host"> Host identifier of Ldap server, or null for
228 /// "localhost".
229 ///
230 /// </param>
231 /// <param name="port"> The port number for Ldap server (use
232 /// LdapConnection.DEFAULT_PORT for default port).
233 ///
234 /// </param>
235 /// <param name="dn"> Distinguished name of the base object of the search.
236 ///
237 ///
238 /// </param>
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.
244 ///
245 ///
246 /// </param>
247 /// <param name="scope"> Depth of search (in DN namespace). Use one of
248 /// SCOPE_BASE, SCOPE_ONE, SCOPE_SUB from LdapConnection.
249 ///
250 ///
251 /// </param>
252 /// <param name="filter"> The search filter specifying the search criteria.
253 ///
254 ///
255 /// </param>
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.
263 /// </param>
264 public LdapUrl(System.String host, int port, System.String dn, System.String[] attrNames, int scope, System.String filter, System.String[] extensions)
266 InitBlock();
267 this.host = host;
268 this.port = port;
269 this.dn = dn;
270 this.attrs = new System.String[attrNames.Length];
271 attrNames.CopyTo(this.attrs, 0);
272 this.scope = scope;
273 this.filter = filter;
274 this.extensions = new System.String[extensions.Length];
275 extensions.CopyTo(this.extensions, 0);
276 return ;
279 /// <summary> Constructs an Ldap URL with all fields explicitly assigned, including
280 /// isSecure, to specify an Ldap search operation.
281 ///
282 /// </summary>
283 /// <param name="host"> Host identifier of Ldap server, or null for
284 /// "localhost".
285 ///
286 ///
287 /// </param>
288 /// <param name="port"> The port number for Ldap server (use
289 /// LdapConnection.DEFAULT_PORT for default port).
290 ///
291 ///
292 /// </param>
293 /// <param name="dn"> Distinguished name of the base object of the search.
294 ///
295 ///
296 /// </param>
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.
302 ///
303 ///
304 /// </param>
305 /// <param name="scope"> Depth of search (in DN namespace). Use one of
306 /// SCOPE_BASE, SCOPE_ONE, SCOPE_SUB from LdapConnection.
307 ///
308 ///
309 /// </param>
310 /// <param name="filter"> The search filter specifying the search criteria.
311 /// from LdapConnection: SCOPE_BASE, SCOPE_ONE, SCOPE_SUB.
312 ///
313 ///
314 /// </param>
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.
322 ///
323 ///
324 /// </param>
325 /// <param name="secure"> If true creates an Ldap URL of the ldaps type
326 /// </param>
327 public LdapUrl(System.String host, int port, System.String dn, System.String[] attrNames, int scope, System.String filter, System.String[] extensions, bool secure)
329 InitBlock();
330 this.host = host;
331 this.port = port;
332 this.dn = dn;
333 this.attrs = attrNames;
334 this.scope = scope;
335 this.filter = filter;
336 this.extensions = new System.String[extensions.Length];
337 extensions.CopyTo(this.extensions, 0);
338 this.secure = secure;
339 return ;
342 /// <summary> Returns a clone of this URL object.
343 ///
344 /// </summary>
345 /// <returns> clone of this URL object.
346 /// </returns>
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.
360 ///
361 /// Any occurences of %HH are decoded to the hex value represented.
362 /// However, this method does NOT decode "+" into " ".
363 ///
364 /// </summary>
365 /// <param name="URLEncoded"> String to decode.
366 ///
367 /// </param>
368 /// <returns> The decoded string.
369 ///
370 /// </returns>
371 /// <exception> MalformedURLException The URL could not be parsed.
372 /// </exception>
373 public static System.String decode(System.String URLEncoded)
377 int searchStart = 0;
378 int fieldStart;
380 fieldStart = URLEncoded.IndexOf("%", searchStart);
381 // Return now if no encoded data
382 if (fieldStart < 0)
384 return URLEncoded;
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);
393 while (true)
395 if (fieldStart > (dataLen - 3))
397 throw new System.UriFormatException("LdapUrl.decode: must be two hex characters following escape character '%'");
399 if (fieldStart < 0)
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)));
403 fieldStart += 1;
404 if (fieldStart >= dataLen)
405 break;
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)
417 break;
418 fieldStart = URLEncoded.IndexOf("%", searchStart);
421 return (decoded.ToString());
424 /// <summary> Encodes an arbitrary string using the URL encoding rules.
425 ///
426 /// Any illegal characters are encoded as %HH.
427 ///
428 /// </summary>
429 /// <param name="toEncode"> The string to encode.
430 ///
431 /// </param>
432 /// <returns> The URL-encoded string.
433 ///
434 /// Comment: An illegal character consists of any non graphical US-ASCII character, Unsafe, or reserved characters.
435 /// </returns>
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'
439 System.String temp;
440 char currChar;
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.
450 else
451 buffer.Append("%" + System.Convert.ToString(currChar, 16));
453 else
454 buffer.Append(currChar);
456 return buffer.ToString();
459 /// <summary> Returns the base distinguished name encapsulated in the URL.
460 ///
461 /// </summary>
462 /// <returns> The base distinguished name specified in the URL, or null if none.
463 /// </returns>
464 public virtual System.String getDN()
466 return dn;
469 /// <summary> Sets the base distinguished name encapsulated in the URL.</summary>
470 /* package */
471 internal virtual void setDN(System.String dn)
473 this.dn = dn;
474 return ;
477 /// <summary> Returns a valid string representation of this Ldap URL.
478 ///
479 /// </summary>
480 /// <returns> The string representation of the Ldap URL.
481 /// </returns>
482 public override System.String ToString()
484 System.Text.StringBuilder url = new System.Text.StringBuilder(256);
485 // Scheme
486 if (secure)
488 url.Append("ldaps://");
490 else
492 url.Append("ldap://");
494 // Host:port/dn
495 if (ipV6)
497 url.Append("[" + host + "]");
499 else
501 url.Append(host);
504 // Port not specified
505 if (port != 0)
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();
515 url.Append("/");
517 if ((System.Object) dn != null)
519 url.Append(dn);
522 if ((attrs == null) && (scope == DEFAULT_SCOPE) && ((System.Object) filter == null) && (extensions == null))
524 return url.ToString();
527 // attributes
528 url.Append("?");
529 if (attrs != null)
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))
537 url.Append(",");
542 if ((scope == DEFAULT_SCOPE) && ((System.Object) filter == null) && (extensions == null))
544 return url.ToString();
547 // scope
548 url.Append("?");
549 if (scope != DEFAULT_SCOPE)
551 if (scope == LdapConnection.SCOPE_ONE)
553 url.Append("one");
555 else
557 url.Append("sub");
561 if (((System.Object) filter == null) && (extensions == null))
563 return url.ToString();
566 // filter
567 if ((System.Object) filter == null)
569 url.Append("?");
571 else
573 url.Append("?" + Filter);
576 if (extensions == null)
578 return url.ToString();
581 // extensions
582 url.Append("?");
583 if (extensions != null)
585 for (int i = 0; i < extensions.Length; i++)
587 url.Append(extensions[i]);
588 if (i < (extensions.Length - 1))
590 url.Append(",");
594 return url.ToString();
597 private System.String[] parseList(System.String listStr, char delimiter, int listStart, int listEnd)
598 // end of list + 1
600 System.String[] list;
601 // Check for and empty string
602 if ((listEnd - listStart) < 1)
604 return null;
606 // First count how many items are specified
607 int itemStart = listStart;
608 int itemEnd;
609 int itemCount = 0;
610 while (itemStart > 0)
612 // itemStart == 0 if no delimiter found
613 itemCount += 1;
614 itemEnd = listStr.IndexOf((System.Char) delimiter, itemStart);
615 if ((itemEnd > 0) && (itemEnd < listEnd))
617 itemStart = itemEnd + 1;
619 else
621 break;
624 // Now fill in the array with the attributes
625 itemStart = listStart;
626 list = new System.String[itemCount];
627 itemCount = 0;
628 while (itemStart > 0)
630 itemEnd = listStr.IndexOf((System.Char) delimiter, itemStart);
631 if (itemStart <= listEnd)
633 if (itemEnd < 0)
634 itemEnd = listEnd;
635 if (itemEnd > listEnd)
636 itemEnd = listEnd;
637 list[itemCount] = listStr.Substring(itemStart, (itemEnd) - (itemStart));
638 itemStart = itemEnd + 1;
639 itemCount += 1;
641 else
643 break;
646 return list;
650 private void parseURL(System.String url)
652 int scanStart = 0;
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");
663 scanStart += 1;
664 scanEnd -= 1;
667 // Determine the URL scheme and set appropriate default port
668 if (url.Substring(scanStart, (scanStart + 4) - (scanStart)).ToUpper().Equals("URL:".ToUpper()))
670 scanStart += 4;
672 if (url.Substring(scanStart, (scanStart + 7) - (scanStart)).ToUpper().Equals("ldap://".ToUpper()))
674 scanStart += 7;
675 port = LdapConnection.DEFAULT_PORT;
677 else if (url.Substring(scanStart, (scanStart + 8) - (scanStart)).ToUpper().Equals("ldaps://".ToUpper()))
679 secure = true;
680 scanStart += 8;
681 port = LdapConnection.DEFAULT_SSL_PORT;
683 else
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;
691 bool novell = false;
692 if (dnStart < 0)
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);
703 if (dnStart > 0)
705 if (url[dnStart + 1] == '?')
707 hostPortEnd = dnStart;
708 dnStart += 1;
709 novell = true;
711 else
713 dnStart = - 1;
717 else
719 hostPortEnd = dnStart;
721 // Check for IPV6 "[ipaddress]:port"
722 int portStart;
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))
736 // port is specified
737 port = System.Int32.Parse(url.Substring(portStart + 1, (hostPortEnd) - (portStart + 1)));
739 else
743 else
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));
752 else
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))
762 return ;
764 // Parse out the base dn
765 scanStart = dnStart + 1;
767 int attrsStart = url.IndexOf((System.Char) '?', scanStart);
768 if (attrsStart < 0)
770 dn = url.Substring(scanStart, (scanEnd) - (scanStart));
772 else
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)
780 return ;
782 // Parse out the attributes
783 int scopeStart = url.IndexOf((System.Char) '?', scanStart);
784 if (scopeStart < 0)
785 scopeStart = scanEnd - 1;
786 attrs = parseList(url, ',', attrsStart + 1, scopeStart);
788 scanStart = scopeStart + 1;
789 if (scanStart >= scanEnd)
790 return ;
792 // Parse out the scope
793 int filterStart = url.IndexOf((System.Char) '?', scanStart);
794 System.String scopeStr;
795 if (filterStart < 0)
797 scopeStr = url.Substring(scanStart, (scanEnd) - (scanStart));
799 else
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;
819 else
821 throw new System.UriFormatException("LdapUrl: URL invalid scope");
825 scanStart = filterStart + 1;
826 if ((scanStart >= scanEnd) || (filterStart < 0))
827 return ;
829 // Parse out the filter
830 scanStart = filterStart + 1;
832 System.String filterStr;
833 int extStart = url.IndexOf((System.Char) '?', scanStart);
834 if (extStart < 0)
836 filterStr = url.Substring(scanStart, (scanEnd) - (scanStart));
838 else
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))
851 return ;
853 // Parse out the extensions
854 int end = url.IndexOf((System.Char) '?', scanStart);
855 if (end > 0)
856 throw new System.UriFormatException("LdapUrl: URL has too many ? fields");
857 extensions = parseList(url, ',', scanStart, scanEnd);
859 return ;