Merge pull request #1899 from saper/resgencond
[mono.git] / mcs / class / System / System / Uri.cs
1 //
2 // System.Uri
3 //
4 // Authors:
5 //    Lawrence Pit (loz@cable.a2000.nl)
6 //    Garrett Rooney (rooneg@electricjellyfish.net)
7 //    Ian MacLean (ianm@activestate.com)
8 //    Ben Maurer (bmaurer@users.sourceforge.net)
9 //    Atsushi Enomoto (atsushi@ximian.com)
10 //    Sebastien Pouliot  <sebastien@ximian.com>
11 //    Stephane Delcroix  <stephane@delcroix.org>
12 //
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com)
17 // Copyright (c) 2009 Stephane Delcroix
18 //
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
26 // 
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
29 // 
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 //
38 // See RFC 2396 for more info on URI's.
39 //
40 // TODO: optimize by parsing host string only once
41 //
42 using System.ComponentModel;
43 using System.IO;
44 using System.Net;
45 using System.Runtime.Serialization;
46 using System.Text;
47 using System.Collections;
48 using System.Collections.Generic;
49 using System.Globalization;
50
51 //
52 // Disable warnings on Obsolete methods being used
53 //
54 #pragma warning disable 612
55
56 namespace System {
57
58         [Serializable]
59         [TypeConverter (typeof (UriTypeConverter))]
60         public class Uri : ISerializable {
61                 // NOTES:
62                 // o  scheme excludes the scheme delimiter
63                 // o  port is -1 to indicate no port is defined
64                 // o  path is empty or starts with / when scheme delimiter == "://"
65                 // o  query is empty or starts with ? char, escaped.
66                 // o  fragment is empty or starts with # char, unescaped.
67                 // o  all class variables are in escaped format when they are escapable,
68                 //    except cachedToString.
69                 // o  UNC is supported, as starts with "\\" for windows,
70                 //    or "//" with unix.
71
72                 private string source;
73                 private string scheme = String.Empty;
74                 private string host = String.Empty;
75                 private int port = -1;
76                 private string path = String.Empty;
77                 private string query = String.Empty;
78                 private string fragment = String.Empty;
79                 private string userinfo;
80                 private bool isUnc;
81                 private bool isAbsoluteUri = true;
82                 private long scope_id;
83
84                 private List<string> segments;
85                 
86                 private bool userEscaped;
87                 private string cachedAbsoluteUri;
88                 private string cachedToString;
89                 private string cachedLocalPath;
90                 private int cachedHashCode;
91                 
92                 private static bool s_IriParsing;
93
94                 internal static bool IriParsing {
95                         get { return s_IriParsing; }
96                         set { s_IriParsing = value; }
97                 }
98
99                 // Do not rename this.
100                 // User code might set this to true with reflection.
101                 // When set to true an Uri constructed with UriKind.RelativeOrAbsolute 
102                 // and paths such as "/foo" is assumed relative.
103                 private static bool useDotNetRelativeOrAbsolute;
104
105 #if BOOTSTRAP_BASIC
106                 private static readonly string hexUpperChars = "0123456789ABCDEF";
107                 private static readonly string [] Empty = new string [0];
108                 private static bool isWin32 = (Path.DirectorySeparatorChar == '\\');
109 #else
110                 static readonly char[] hexUpperChars = new [] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
111 #endif
112         
113                 // Fields
114                 
115                 public static readonly string SchemeDelimiter = "://";
116                 public static readonly string UriSchemeFile = "file";
117                 public static readonly string UriSchemeFtp = "ftp";
118                 public static readonly string UriSchemeGopher = "gopher";
119                 public static readonly string UriSchemeHttp = "http";
120                 public static readonly string UriSchemeHttps = "https";
121                 public static readonly string UriSchemeMailto = "mailto";
122                 public static readonly string UriSchemeNews = "news";
123                 public static readonly string UriSchemeNntp = "nntp";
124                 public static readonly string UriSchemeNetPipe = "net.pipe";
125                 public static readonly string UriSchemeNetTcp = "net.tcp";
126
127                 internal static readonly string UriSchemeTelnet = "telnet";
128                 internal static readonly string UriSchemeLdap = "ldap";
129                 internal static readonly string UriSchemeUuid = "uuid";
130
131                 private static readonly string [] knownUriSchemes =
132                 {
133                         UriSchemeFile,
134                         UriSchemeFtp,
135                         UriSchemeGopher,
136                         UriSchemeHttp,
137                         UriSchemeHttps,
138                         UriSchemeMailto,
139                         UriSchemeNews,
140                         UriSchemeNntp,
141                         UriSchemeNetPipe,
142                         UriSchemeNetTcp
143                 };
144
145                 // Constructors
146
147                 static Uri ()
148                 {
149                         IriParsing = true;
150
151                         var iriparsingVar = Environment.GetEnvironmentVariable ("MONO_URI_IRIPARSING");
152                         if (iriparsingVar == "true")
153                                 IriParsing = true;
154                         else if (iriparsingVar == "false")
155                                 IriParsing = false;
156
157                         useDotNetRelativeOrAbsolute = Environment.GetEnvironmentVariable ("MONO_URI_DOTNETRELATIVEORABSOLUTE") == "true";
158                 }
159
160                 public Uri (string uriString) : this (uriString, false) 
161                 {
162                 }
163
164                 protected Uri (SerializationInfo serializationInfo, StreamingContext streamingContext)
165                 {
166                         string uri = serializationInfo.GetString ("AbsoluteUri");
167                         if (uri.Length > 0) {
168                                 source = uri;
169                                 ParseUri (UriKind.Absolute);
170                         } else {
171                                 uri = serializationInfo.GetString ("RelativeUri");
172                                 if (uri.Length > 0) {
173                                         source = uri;
174                                         ParseUri (UriKind.Relative);
175                                 } else {
176                                         throw new ArgumentException ("Uri string was null or empty.");
177                                 }
178                         }
179                 }
180
181                 // When used instead of UriKind.RelativeOrAbsolute paths such as "/foo" are assumed relative.
182                 const UriKind DotNetRelativeOrAbsolute = (UriKind) 300;
183
184                 private void ProcessUriKind (string uriString, ref UriKind uriKind)
185                 {
186                         if (uriString == null)
187                            return;
188                 
189                         if (uriKind == DotNetRelativeOrAbsolute ||
190                                 (uriKind == UriKind.RelativeOrAbsolute && useDotNetRelativeOrAbsolute))
191                                 uriKind = (uriString.StartsWith ("/", StringComparison.Ordinal))? UriKind.Relative : UriKind.RelativeOrAbsolute;
192                 }
193
194                 public Uri (string uriString, UriKind uriKind)
195                 {
196                         source = uriString;
197
198                         ProcessUriKind (uriString, ref uriKind);
199
200                         ParseUri (uriKind);
201
202                         switch (uriKind) {
203                         case UriKind.Absolute:
204                                 if (!IsAbsoluteUri)
205                                         throw new UriFormatException ("Invalid URI: The format of the URI could not be "
206                                                 + "determined.");
207                                 break;
208                         case UriKind.Relative:
209                                 if (IsAbsoluteUri)
210                                         throw new UriFormatException ("Invalid URI: The format of the URI could not be "
211                                                 + "determined because the parameter 'uriString' represents an absolute URI.");
212                                 break;
213                         case UriKind.RelativeOrAbsolute:
214                                 break;
215                         default:
216                                 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
217                                 throw new ArgumentException (msg);
218                         }
219                 }
220
221                 //
222                 // An exception-less constructor, returns success
223                 // condition on the out parameter `success'.
224                 //
225                 Uri (string uriString, UriKind uriKind, out bool success)
226                 {
227                         if (uriString == null) {
228                                 success = false;
229                                 return;
230                         }
231
232                         ProcessUriKind (uriString, ref uriKind);
233
234                         if (uriKind != UriKind.RelativeOrAbsolute &&
235                                 uriKind != UriKind.Absolute &&
236                                 uriKind != UriKind.Relative) {
237                                 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
238                                 throw new ArgumentException (msg);
239                         }
240
241                         source = uriString;
242                         if (ParseNoExceptions (uriKind, uriString) != null)
243                                 success = false;
244                         else {
245                                 success = true;
246                                 
247                                 switch (uriKind) {
248                                 case UriKind.Absolute:
249                                         if (!IsAbsoluteUri)
250                                                 success = false;
251                                         break;
252                                 case UriKind.Relative:
253                                         if (IsAbsoluteUri)
254                                                 success = false;
255                                         break;
256                                 case UriKind.RelativeOrAbsolute:
257                                         break;
258                                 default:
259                                         success = false;
260                                         break;
261                                 }
262
263                                 if (success && host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
264                                         // host name present (but not an IPv6 address)
265                                         host = host.ToLower (CultureInfo.InvariantCulture);
266                                 }
267                         }
268                 }
269
270                 public Uri (Uri baseUri, Uri relativeUri)
271                 {
272                         Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
273                         // FIXME: this should call UriParser.Resolve
274                 }
275
276                 // note: doc says that dontEscape is always false but tests show otherwise
277                 [Obsolete]
278                 public Uri (string uriString, bool dontEscape) 
279                 {
280                         userEscaped = dontEscape;
281                         source = uriString;
282                         ParseUri (UriKind.Absolute);
283                         if (!isAbsoluteUri)
284                                 throw new UriFormatException ("Invalid URI: The format of the URI could not be "
285                                         + "determined: " + uriString);
286                 }
287
288                 public Uri (Uri baseUri, string relativeUri) 
289                 {
290                         Merge (baseUri, relativeUri);
291                         // FIXME: this should call UriParser.Resolve
292                 }
293
294                 [Obsolete ("dontEscape is always false")]
295                 public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
296                 {
297                         userEscaped = dontEscape;
298                         Merge (baseUri, relativeUri);
299                 }
300
301                 private void Merge (Uri baseUri, string relativeUri)
302                 {
303                         if (baseUri == null)
304                                 throw new ArgumentNullException ("baseUri");
305                         if (!baseUri.IsAbsoluteUri)
306                                 throw new ArgumentOutOfRangeException ("baseUri");
307                         if (string.IsNullOrEmpty (relativeUri)) {
308                                 source = baseUri.OriginalString;
309                                 ParseUri (UriKind.Absolute);
310                                 return;
311                         }
312
313                         string error;
314                         bool startsWithSlash = false;
315
316                         UriElements baseEl;
317                         if (!UriParseComponents.TryParseComponents (baseUri.OriginalString, UriKind.Absolute, out baseEl, out error))
318                                 throw new UriFormatException (error);
319
320                         if (relativeUri.StartsWith (baseEl.scheme + ":", StringComparison.Ordinal))
321                                 relativeUri = relativeUri.Substring (baseEl.scheme.Length + 1);
322
323                         if (relativeUri.Length >= 1 && relativeUri [0] == '/') {
324                                 if (relativeUri.Length >= 2 && relativeUri [1] == '/') {
325                                         source = baseEl.scheme + ":" + relativeUri;
326                                         ParseUri (UriKind.Absolute);
327                                         return;
328                                 }
329
330                                 relativeUri = relativeUri.Substring (1);
331                                 startsWithSlash = true;
332                         }
333
334                         UriElements relativeEl;
335                         if (!UriParseComponents.TryParseComponents (relativeUri, UriKind.RelativeOrAbsolute, out relativeEl, out error))
336                                 throw new UriFormatException (error);
337
338                         if (relativeEl.isAbsoluteUri) {
339                                 source = relativeUri;
340                                 ParseUri (UriKind.Absolute);
341                                 return;
342                         }
343
344                         source = baseEl.scheme + baseEl.delimiter;
345
346                         if (baseEl.user != null)
347                                 source += baseEl.user + "@";
348
349                         source += baseEl.host;
350
351                         if (baseEl.port >= 0)
352                                 source += ":" + baseEl.port.ToString (CultureInfo.InvariantCulture);
353
354                         var canUseBase = true;
355
356                         string path;
357                         if (!string.IsNullOrEmpty (relativeEl.path) || startsWithSlash) {
358                                 canUseBase = false;
359                                 path = relativeEl.path;
360                                 if (startsWithSlash)
361                                         path = relativeEl.path;
362                                 else {
363                                         var pathEnd = baseEl.path.LastIndexOf ('/');
364                                         path = (pathEnd > 0)? baseEl.path.Substring (0, pathEnd+1) : "";
365                                         path += relativeEl.path;
366                                 }
367                         } else {
368                                 path = baseEl.path;
369                         }
370
371                         if ((path.Length == 0 || path [0] != '/') && baseEl.delimiter == SchemeDelimiter)
372                                 path = "/" + path;
373
374                         source += UriHelper.Reduce (path, true);
375
376                         if (relativeEl.query != null) {
377                                 canUseBase = false;
378                                 source += "?" + relativeEl.query;
379                         } else if (canUseBase && baseEl.query != null)
380                                 source += "?" + baseEl.query;
381
382                         if (relativeEl.fragment != null)
383                                 source += "#" + relativeEl.fragment;
384                         else if (canUseBase && baseEl.fragment != null)
385                                 source += "#" + baseEl.fragment;
386
387                         ParseUri (UriKind.Absolute);
388
389                         return;
390                 }
391                 
392                 // Properties
393                 
394                 public string AbsolutePath { 
395                         get {
396                                 EnsureAbsoluteUri ();
397                                 if (scheme == "mailto" || scheme == "file")
398                                         // faster (mailto) and special (file) cases
399                                         return path;
400                                 
401                                 if (path.Length == 0) {
402                                         string start = scheme + SchemeDelimiter;
403                                         if (path.StartsWith (start, StringComparison.Ordinal))
404                                                 return "/";
405                                         else
406                                                 return String.Empty;
407                                 }
408                                 return path;
409                         }
410                 }
411
412                 public string AbsoluteUri { 
413                         get { 
414                                 EnsureAbsoluteUri ();
415
416                                 if (cachedAbsoluteUri == null)
417                                         cachedAbsoluteUri = GetComponents (UriComponents.AbsoluteUri, UriFormat.UriEscaped);
418
419                                 return cachedAbsoluteUri;
420                         } 
421                 }
422
423                 public string Authority { 
424                         get { 
425                                 EnsureAbsoluteUri ();
426                                 return (GetDefaultPort (Scheme) == port)
427                                      ? host : host + ":" + port;
428                         } 
429                 }
430
431                 public string Fragment { 
432                         get { 
433                                 EnsureAbsoluteUri ();
434                                 return fragment; 
435                         } 
436                 }
437
438                 public string Host { 
439                         get { 
440                                 EnsureAbsoluteUri ();
441                                 return host; 
442                         } 
443                 }
444
445                 public UriHostNameType HostNameType { 
446                         get {
447                                 EnsureAbsoluteUri ();
448                                 UriHostNameType ret = CheckHostName (Host);
449                                 if (ret != UriHostNameType.Unknown)
450                                         return ret;
451
452                                 if (scheme == "mailto")
453                                         return UriHostNameType.Basic;
454                                 return (IsFile) ? UriHostNameType.Basic : ret;
455                         } 
456                 }
457
458                 public bool IsDefaultPort { 
459                         get {
460                                 EnsureAbsoluteUri ();
461                                 return GetDefaultPort (Scheme) == port;
462                         }
463                 }
464
465                 public bool IsFile { 
466                         get {
467                                 EnsureAbsoluteUri ();
468                                 return (Scheme == UriSchemeFile);
469                         }
470                 }
471
472                 public bool IsLoopback { 
473                         get {
474                                 EnsureAbsoluteUri ();
475                                 
476                                 if (Host.Length == 0) {
477                                         return IsFile;
478                                 }
479
480                                 if (host == "loopback" || host == "localhost") 
481                                         return true;
482
483                                 IPAddress result;
484                                 if (IPAddress.TryParse (host, out result))
485                                         if (IPAddress.Loopback.Equals (result))
486                                                 return true;
487
488                                 IPv6Address result6;
489                                 if (IPv6Address.TryParse (host, out result6)){
490                                         if (IPv6Address.IsLoopback (result6))
491                                                 return true;
492                                 }
493
494                                 return false;
495                         } 
496                 }
497
498                 public bool IsUnc {
499                         // rule: This should be true only if
500                         //   - uri string starts from "\\", or
501                         //   - uri string starts from "//" (Samba way)
502                         get { 
503                                 EnsureAbsoluteUri ();
504                                 return isUnc; 
505                         } 
506                 }
507
508                 private bool IsLocalIdenticalToAbsolutePath ()
509                 {
510                         if (IsFile)
511                                 return false;
512
513                         if ((scheme == Uri.UriSchemeNews) || (scheme == Uri.UriSchemeNntp) || (scheme == Uri.UriSchemeFtp))
514                                 return false;
515
516                         return IsWellFormedOriginalString ();
517                 }
518
519                 public string LocalPath { 
520                         get {
521                                 EnsureAbsoluteUri ();
522                                 if (cachedLocalPath != null)
523                                         return cachedLocalPath;
524
525                                 var formatFlags = UriHelper.FormatFlags.NoSlashReplace;
526
527                                 if (userEscaped)
528                                         formatFlags |= UriHelper.FormatFlags.UserEscaped;
529
530                                 string unescapedPath = UriHelper.FormatAbsolute (path, scheme,
531                                         UriComponents.Path, UriFormat.Unescaped, formatFlags);
532
533                                 if (path.StartsWith ("/", StringComparison.Ordinal) &&
534                                         !unescapedPath.StartsWith ("/", StringComparison.Ordinal))
535                                         unescapedPath = "/" + unescapedPath;
536
537                                 if (IsLocalIdenticalToAbsolutePath ()) {
538                                         cachedLocalPath = unescapedPath;
539                                         return cachedLocalPath;
540                                 }
541
542                                 if (!IsUnc) {
543                                         bool windows = (path.Length > 3 && path [1] == ':' &&
544                                                 (path [2] == '\\' || path [2] == '/'));
545
546                                         if (windows)
547                                                 cachedLocalPath = unescapedPath.Replace ('/', '\\');
548                                         else
549                                                 cachedLocalPath = unescapedPath;
550                                 } else {
551                                         // support *nix and W32 styles
552                                         if (path.Length > 1 && path [1] == ':')
553                                                 cachedLocalPath = unescapedPath.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
554
555                                         // LAMESPEC: ok, now we cannot determine
556                                         // if such URI like "file://foo/bar" is
557                                         // Windows UNC or unix file path, so
558                                         // they should be handled differently.
559                                         else if (System.IO.Path.DirectorySeparatorChar == '\\') {
560                                                 string h = host;
561                                                 if (path.Length > 0) {
562                                                         if ((path.Length > 1) || (path[0] != '/')) {
563                                                                 h += unescapedPath.Replace ('/', '\\');
564                                                         }
565                                                 }
566                                                 cachedLocalPath = "\\\\" + h;
567                                         }  else
568                                                 cachedLocalPath = unescapedPath;
569                                 }
570                                 if (cachedLocalPath.Length == 0)
571                                         cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
572                                 return cachedLocalPath;
573                         } 
574                 }
575
576                 public string PathAndQuery { 
577                         get {
578                                 EnsureAbsoluteUri ();
579                                 return path + Query;
580                         } 
581                 }
582
583                 public int Port { 
584                         get { 
585                                 EnsureAbsoluteUri ();
586                                 return port; 
587                         } 
588                 }
589
590                 public string Query { 
591                         get { 
592                                 EnsureAbsoluteUri ();
593                                 return query; 
594                         }
595                 }
596
597                 public string Scheme { 
598                         get { 
599                                 EnsureAbsoluteUri ();
600                                 return scheme; 
601                         } 
602                 }
603
604                 public string [] Segments { 
605                         get { 
606                                 EnsureAbsoluteUri ();
607
608                                 // return a (pre-allocated) empty array
609                                 if (path.Length == 0)
610 #if BOOTSTRAP_BASIC
611                                         return Empty;
612 #else
613                                         return EmptyArray<string>.Value;
614 #endif
615                                 // do not return the original array (since items can be changed)
616                                 if (segments != null)
617                                         return segments.ToArray ();
618
619                                 List<string> list = new List<string> ();
620                                 StringBuilder current = new StringBuilder ();
621                                 for (int i = 0; i < path.Length; i++) {
622                                         switch (path [i]) {
623                                         case '/':
624                                         case '\\':
625                                                 current.Append (path [i]);
626                                                 list.Add (current.ToString ());
627                                                 current.Length = 0;
628                                                 break;
629                                         case '%':
630                                                 if ((i < path.Length - 2) && (path [i + 1] == '5' && path [i + 2] == 'C')) {
631                                                         current.Append ("%5C");
632                                                         list.Add (current.ToString ());
633                                                         current.Length = 0;
634                                                         i += 2;
635                                                 } else {
636                                                         current.Append ('%');
637                                                 }
638                                                 break;
639                                         default:
640                                                 current.Append (path [i]);
641                                                 break;
642                                         }
643                                 }
644
645                                 if (current.Length > 0)
646                                         list.Add (current.ToString ());
647
648                                 if (IsFile && (list.Count > 0)) {
649                                         string first = list [0];
650                                         if ((first.Length > 1) && (first [1] == ':')) {
651                                                 list.Insert (0, "/");
652                                         }
653                                 }
654                                 segments = list;
655                                 return segments.ToArray ();
656                         } 
657                 }
658
659                 public bool UserEscaped { 
660                         get { return userEscaped; } 
661                 }
662
663                 public string UserInfo { 
664                         get { 
665                                 EnsureAbsoluteUri ();
666                                 return userinfo == null ? String.Empty : userinfo;
667                         }
668                 }
669                 
670                 public string DnsSafeHost {
671                         get {
672                                 EnsureAbsoluteUri ();
673                                 string host = Host;
674                                 if (HostNameType == UriHostNameType.IPv6) {
675                                         host = Host.Substring (1, Host.Length - 2);
676                                         if (scope_id != 0)
677                                                 host += "%" + scope_id.ToString ();
678                                 }
679                                 return Unescape (host);
680                         }
681                 }
682
683                 public bool IsAbsoluteUri {
684                         get { return isAbsoluteUri; }
685                 }
686
687                 public string OriginalString {
688                         get { return source; }
689                 }
690
691                 // Methods              
692
693                 public static UriHostNameType CheckHostName (string name) 
694                 {
695                         if (name == null || name.Length == 0)
696                                 return UriHostNameType.Unknown;
697
698                         if (IsIPv4Address (name)) 
699                                 return UriHostNameType.IPv4;
700                                 
701                         if (IsDomainAddress (name))
702                                 return UriHostNameType.Dns;                             
703                                 
704                         IPv6Address addr;
705                         if (IPv6Address.TryParse (name, out addr))
706                                 return UriHostNameType.IPv6;
707                         
708                         return UriHostNameType.Unknown;
709                 }
710                 
711                 internal static bool IsIPv4Address (string name)
712                 {
713                         string [] captures = name.Split (new char [] {'.'});
714                         if (captures.Length != 4)
715                                 return false;
716
717                         for (int i = 0; i < 4; i++) {
718                                 int length;
719
720                                 length = captures [i].Length;
721                                 if (length == 0)
722                                         return false;
723                                 uint number;
724                                 if (!UInt32.TryParse (captures [i], out number))
725                                         return false;
726                                 if (number > 255)
727                                         return false;
728                         }
729                         return true;
730                 }                       
731                                 
732                 internal static bool IsDomainAddress (string name)
733                 {
734                         int len = name.Length;
735                         
736                         int count = 0;
737                         for (int i = 0; i < len; i++) {
738                                 char c = name [i];
739                                 if (count == 0) {
740                                         if (!Char.IsLetterOrDigit (c))
741                                                 return false;
742                                 } else if (c == '.') {
743                                         // www..host.com is bad
744                                         if (i + 1 < len && name [i + 1] == '.')
745                                                 return false;
746                                         count = 0;
747                                         continue;
748                                 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
749                                         return false;
750                                 }
751                                 if (++count == 64)
752                                         return false;
753                         }
754                         
755                         return true;
756                 }
757 #if !NET_2_1
758
759                 [Obsolete ("This method does nothing, it has been obsoleted")]
760                 protected virtual void Canonicalize ()
761                 {
762                         //
763                         // This is flagged in the Microsoft documentation as used
764                         // internally, no longer in use, and Obsolete.
765                         //
766                 }
767
768                 [MonoTODO ("Find out what this should do")]
769                 [Obsolete]
770                 protected virtual void CheckSecurity ()
771                 {
772                 }
773
774 #endif // NET_2_1
775
776                 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
777                 public static bool CheckSchemeName (string schemeName) 
778                 {
779                         if (schemeName == null || schemeName.Length == 0)
780                                 return false;
781                         
782                         if (!IsAlpha (schemeName [0]))
783                                 return false;
784
785                         int len = schemeName.Length;
786                         for (int i = 1; i < len; i++) {
787                                 char c = schemeName [i];
788                                 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
789                                         return false;
790                         }
791                         
792                         return true;
793                 }
794
795                 private static bool IsAlpha (char c)
796                 {
797                         // as defined in rfc2234
798                         // %x41-5A / %x61-7A (A-Z / a-z)
799                         int i = (int) c;
800                         return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
801                 }
802
803                 public override bool Equals (object comparand) 
804                 {
805                         if (comparand == null) 
806                                 return false;
807
808                         Uri uri = comparand as Uri;
809                         if ((object) uri == null) {
810                                 string s = comparand as String;
811                                 if (s == null)
812                                         return false;
813
814                                 if (!TryCreate (s, UriKind.RelativeOrAbsolute, out uri))
815                                         return false;
816                         }
817
818                         return InternalEquals (uri);
819                 }
820
821                 // Assumes: uri != null
822                 bool InternalEquals (Uri uri)
823                 {
824                         if (this.isAbsoluteUri != uri.isAbsoluteUri)
825                                 return false;
826                         if (!this.isAbsoluteUri)
827                                 return this.source == uri.source;
828
829                         CultureInfo inv = CultureInfo.InvariantCulture;
830                         return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
831                                 && this.host.ToLower (inv) == uri.host.ToLower (inv)
832                                 && this.port == uri.port
833                                 && this.query == uri.query
834                                 && this.path == uri.path;
835                 }
836
837                 public static bool operator == (Uri uri1, Uri uri2)
838                 {
839                         return object.Equals (uri1, uri2);
840                 }
841
842                 public static bool operator != (Uri uri1, Uri uri2)
843                 {
844                         return !(uri1 == uri2);
845                 }
846
847                 public override int GetHashCode () 
848                 {
849                         if (cachedHashCode == 0) {
850                                 CultureInfo inv = CultureInfo.InvariantCulture;
851                                 if (isAbsoluteUri) {
852                                         cachedHashCode = scheme.ToLower (inv).GetHashCode ()
853                                                 ^ host.ToLower (inv).GetHashCode ()
854                                                 ^ port
855                                                 ^ query.GetHashCode ()
856                                                 ^ path.GetHashCode ();
857                                 }
858                                 else {
859                                         cachedHashCode = source.GetHashCode ();
860                                 }
861                         }
862                         return cachedHashCode;
863                 }
864                 
865                 public string GetLeftPart (UriPartial part) 
866                 {
867                         EnsureAbsoluteUri ();
868                         int defaultPort;
869                         switch (part) {                         
870                         case UriPartial.Scheme : 
871                                 return scheme + GetOpaqueWiseSchemeDelimiter ();
872                         case UriPartial.Authority :
873                                 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
874                                         return String.Empty;
875                                         
876                                 StringBuilder s = new StringBuilder ();
877                                 s.Append (scheme);
878                                 s.Append (GetOpaqueWiseSchemeDelimiter ());
879                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
880                                         s.Append ('/');  // win32 file
881                                 if (userinfo != null) 
882                                         s.Append (userinfo).Append ('@');
883                                 s.Append (host);
884                                 defaultPort = GetDefaultPort (scheme);
885                                 if ((port != -1) && (port != defaultPort))
886                                         s.Append (':').Append (port);                    
887                                 return s.ToString ();                           
888                         case UriPartial.Path :
889                                 StringBuilder sb = new StringBuilder ();
890                                 sb.Append (scheme);
891                                 sb.Append (GetOpaqueWiseSchemeDelimiter ());
892                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
893                                         sb.Append ('/');  // win32 file
894                                 if (userinfo != null) 
895                                         sb.Append (userinfo).Append ('@');
896                                 sb.Append (host);
897                                 defaultPort = GetDefaultPort (scheme);
898                                 if ((port != -1) && (port != defaultPort))
899                                         sb.Append (':').Append (port);
900
901                                 if (path.Length > 0) {
902                                         if (scheme == "mailto" || scheme == "news")
903                                                 sb.Append (path);
904                                         else 
905                                                 sb.Append (Reduce (path, CompactEscaped (scheme)));
906                                 }
907                                 return sb.ToString ();
908                         }
909                         return null;
910                 }
911
912                 public static int FromHex (char digit) 
913                 {
914                         if ('0' <= digit && digit <= '9') {
915                                 return (int) (digit - '0');
916                         }
917                                 
918                         if ('a' <= digit && digit <= 'f')
919                                 return (int) (digit - 'a' + 10);
920
921                         if ('A' <= digit && digit <= 'F')
922                                 return (int) (digit - 'A' + 10);
923                                 
924                         throw new ArgumentException ("digit");
925                 }
926
927                 public static string HexEscape (char character) 
928                 {
929                         if (character > 255) {
930                                 throw new ArgumentOutOfRangeException ("character");
931                         }
932                         
933                         return "%" + hexUpperChars [((character & 0xf0) >> 4)] 
934                                    + hexUpperChars [((character & 0x0f))];
935                 }
936
937                 public static char HexUnescape (string pattern, ref int index) 
938                 {
939                         if (pattern == null) 
940                                 throw new ArgumentException ("pattern");
941                                 
942                         if (index < 0 || index >= pattern.Length)
943                                 throw new ArgumentOutOfRangeException ("index");
944
945                         if (!IsHexEncoding (pattern, index))
946                                 return pattern [index++];
947
948                         index++;
949                         int msb = FromHex (pattern [index++]);
950                         int lsb = FromHex (pattern [index++]);
951                         return (char) ((msb << 4) | lsb);
952                 }
953
954                 public static bool IsHexDigit (char character) 
955                 {
956                         return (('0' <= character && character <= '9') ||
957                                 ('a' <= character && character <= 'f') ||
958                                 ('A' <= character && character <= 'F'));
959                 }
960
961                 public static bool IsHexEncoding (string pattern, int index) 
962                 {
963                         if ((index + 3) > pattern.Length)
964                                 return false;
965
966                         return ((pattern [index++] == '%') &&
967                                 IsHexDigit (pattern [index++]) &&
968                                 IsHexDigit (pattern [index]));
969                 }
970
971                 //
972                 // Implemented by copying most of the MakeRelative code
973                 //
974                 public Uri MakeRelativeUri (Uri uri)
975                 {
976                         if (uri == null)
977                                 throw new ArgumentNullException ("uri");
978                         if (Host != uri.Host || Scheme != uri.Scheme)
979                                 return uri;
980
981                         string result = String.Empty;
982                         if (this.path != uri.path){
983                                 string [] segments = this.Segments;
984                                 string [] segments2 = uri.Segments;
985                                 
986                                 int k = 0;
987                                 int max = Math.Min (segments.Length, segments2.Length);
988                                 for (; k < max; k++)
989                                         if (segments [k] != segments2 [k]) 
990                                                 break;
991                                 
992                                 for (int i = k; i < segments.Length && segments [i].EndsWith ("/", StringComparison.Ordinal); i++)
993                                         result += "../";
994                                 for (int i = k; i < segments2.Length; i++)
995                                         result += segments2 [i];
996                                 
997                                 if (result == string.Empty)
998                                         result = "./";
999                         }
1000                         uri.AppendQueryAndFragment (ref result);
1001
1002                         return new Uri (result, UriKind.Relative);
1003                 }
1004
1005                 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1006                 public string MakeRelative (Uri toUri) 
1007                 {
1008                         if ((this.Scheme != toUri.Scheme) ||
1009                             (this.Authority != toUri.Authority))
1010                                 return toUri.ToString ();
1011
1012                         string result = String.Empty;
1013                         if (this.path != toUri.path){
1014                                 string [] segments = this.Segments;
1015                                 string [] segments2 = toUri.Segments;
1016                                 int k = 0;
1017                                 int max = Math.Min (segments.Length, segments2.Length);
1018                                 for (; k < max; k++)
1019                                         if (segments [k] != segments2 [k]) 
1020                                                 break;
1021                                 
1022                                 for (int i = k + 1; i < segments.Length; i++)
1023                                         result += "../";
1024                                 for (int i = k; i < segments2.Length; i++)
1025                                         result += segments2 [i];
1026                         }
1027
1028                         // Important: MakeRelative does not append fragment or query.
1029
1030                         return result;
1031                 }
1032
1033                 void AppendQueryAndFragment (ref string result)
1034                 {
1035                         if (query.Length > 0) {
1036                                 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true, false) : Unescape (query, false);
1037                                 result += q;
1038                         }
1039                         if (fragment.Length > 0)
1040                                 result += Unescape (fragment, true, false);
1041                 }
1042                 
1043                 public override string ToString () 
1044                 {
1045                         if (cachedToString != null) 
1046                                 return cachedToString;
1047
1048                         if (isAbsoluteUri) {
1049                                 if (Parser is DefaultUriParser)
1050                                         cachedToString = Parser.GetComponentsHelper (this, UriComponents.AbsoluteUri, UriHelper.ToStringUnescape);
1051                                 else
1052                                         cachedToString = Parser.GetComponents (this, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
1053                         } else
1054                                 cachedToString = UriHelper.FormatRelative (source, scheme, UriHelper.ToStringUnescape);
1055
1056                         return cachedToString;
1057                 }
1058
1059                 protected void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
1060                 {
1061                         if (this.isAbsoluteUri) {
1062                                 serializationInfo.AddValue ("AbsoluteUri", this.AbsoluteUri);
1063                         } else {
1064                                 serializationInfo.AddValue ("AbsoluteUri", String.Empty);
1065                                 serializationInfo.AddValue ("RelativeUri", this.OriginalString);
1066                         }
1067                 }
1068
1069                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1070                 {
1071                         GetObjectData (info, context);
1072                 }
1073
1074
1075                 // Internal Methods             
1076
1077                 [Obsolete]
1078                 protected virtual void Escape ()
1079                 {
1080                         path = EscapeString (path);
1081                 }
1082
1083                 [Obsolete]
1084                 protected static string EscapeString (string str) 
1085                 {
1086                         return EscapeString (str, Uri.EscapeCommonHexBrackets);
1087                 }
1088
1089                 private const string EscapeCommon = "<>%\"{}|\\^`";
1090                 private const string EscapeReserved = ";/?:@&=+$,";
1091                 private const string EscapeFragment = "#";
1092                 private const string EscapeBrackets = "[]";
1093
1094                 private const string EscapeNews = EscapeCommon + EscapeBrackets + "?";
1095                 private const string EscapeCommonHex = EscapeCommon + EscapeFragment;
1096                 private const string EscapeCommonBrackets = EscapeCommon + EscapeBrackets;
1097                 internal const string EscapeCommonHexBrackets = EscapeCommon + EscapeFragment + EscapeBrackets;
1098                 internal const string EscapeCommonHexBracketsQuery = EscapeCommonHexBrackets + "?";
1099
1100                 internal static string EscapeString (string str, string escape)
1101                 {
1102                         return EscapeString (str, escape, true);
1103                 }
1104
1105                 internal static string EscapeString (string str, string escape, bool nonAsciiEscape) 
1106                 {
1107                         if (String.IsNullOrEmpty (str))
1108                                 return String.Empty;
1109                         
1110                         StringBuilder s = new StringBuilder ();
1111                         int len = str.Length;   
1112                         for (int i = 0; i < len; i++) {
1113                                 // reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1114                                 // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1115                                 // control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1116                                 // space       = <US-ASCII coded character 20 hexadecimal>
1117                                 // delims      = "<" | ">" | "#" | "%" | <">
1118                                 // unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1119
1120                                 // check for escape code already placed in str, 
1121                                 // i.e. for encoding that follows the pattern 
1122                                 // "%hexhex" in a string, where "hex" is a digit from 0-9 
1123                                 // or a letter from A-F (case-insensitive).
1124                                 if (IsHexEncoding (str,i)) {
1125                                         // if ,yes , copy it as is
1126                                         s.Append (str.Substring (i, 3));
1127                                         i += 2;
1128                                         continue;
1129                                 }
1130
1131                                 char c = str [i];
1132                                 bool outside_limited_ascii = ((c <= 0x20) || (c >= 0x7f));
1133                                 bool needs_escape = (escape.IndexOf (c) != -1);
1134                                 if (nonAsciiEscape && outside_limited_ascii) {
1135                                         byte [] data = Encoding.UTF8.GetBytes (new char [] { c });
1136                                         int length = data.Length;
1137                                         for (int j = 0; j < length; j++) {
1138                                                 c = (char) data [j];
1139                                                 if (needs_escape || nonAsciiEscape)
1140                                                         s.Append (HexEscape (c));
1141                                                 else
1142                                                         s.Append (c);
1143                                         }
1144                                 } else if (needs_escape) {
1145                                         s.Append (HexEscape (c));
1146                                 } else {
1147                                         s.Append (c);
1148                                 }
1149                         }
1150                         
1151                         return s.ToString ();
1152                 }
1153
1154                 // On .NET 1.x, this method is called from .ctor(). When overriden, we 
1155                 // can avoid the "absolute uri" constraints of the .ctor() by
1156                 // overriding with custom code.
1157                 [Obsolete("The method has been deprecated. It is not used by the system.")]
1158                 protected virtual void Parse ()
1159                 {
1160                 }
1161
1162                 private void ParseUri (UriKind kind)
1163                 {
1164                         Parse (kind, source);
1165
1166                         if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1167                                 // host name present (but not an IPv6 address)
1168                                 host = host.ToLower (CultureInfo.InvariantCulture);
1169                         }
1170                 }
1171
1172                 [Obsolete]
1173                 protected virtual string Unescape (string path)
1174                 {
1175                         var formatFlags = UriHelper.FormatFlags.NoSlashReplace | UriHelper.FormatFlags.NoReduce;
1176                         return UriHelper.FormatAbsolute (path, scheme, UriComponents.Path, UriFormat.Unescaped, formatFlags);
1177                 }
1178
1179                 internal static string Unescape (string str, bool excludeSpecial)
1180                 {
1181                         return Unescape (str, excludeSpecial, excludeSpecial);
1182                 }
1183                 
1184                 internal static string Unescape (string str, bool excludeSpecial, bool excludeBackslash) 
1185                 {
1186                         if (String.IsNullOrEmpty (str))
1187                                 return String.Empty;
1188
1189                         StringBuilder s = new StringBuilder ();
1190                         int len = str.Length;
1191                         for (int i = 0; i < len; i++) {
1192                                 char c = str [i];
1193                                 if (c == '%') {
1194                                         char surrogate;
1195                                         char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1196                                         if (excludeSpecial && x == '#')
1197                                                 s.Append ("%23");
1198                                         else if (excludeSpecial && x == '%')
1199                                                 s.Append ("%25");
1200                                         else if (excludeSpecial && x == '?')
1201                                                 s.Append ("%3F");
1202                                         else if (excludeBackslash && x == '\\')
1203                                                 s.Append ("%5C");
1204                                         else {
1205                                                 s.Append (x);
1206                                                 if (surrogate != char.MinValue)
1207                                                         s.Append (surrogate);
1208                                         }
1209                                         i--;
1210                                 } else
1211                                         s.Append (c);
1212                         }
1213                         return s.ToString ();
1214                 }
1215
1216                 
1217                 // Private Methods
1218                 
1219                 private void ParseAsWindowsUNC (string uriString)
1220                 {
1221                         scheme = UriSchemeFile;
1222                         port = -1;
1223                         fragment = String.Empty;
1224                         query = String.Empty;
1225                         isUnc = true;
1226
1227                         uriString = uriString.TrimStart (new char [] {'\\'});
1228                         int pos = uriString.IndexOf ('\\');
1229                         if (pos > 0) {
1230                                 path = uriString.Substring (pos);
1231                                 host = uriString.Substring (0, pos);
1232                         } else { // "\\\\server"
1233                                 host = uriString;
1234                                 path = String.Empty;
1235                         }
1236                         path = path.Replace ("\\", "/");
1237                 }
1238
1239                 //
1240                 // Returns null on success, string with error on failure
1241                 //
1242                 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1243                 {
1244                         if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1245                                 return "Relative file path is not allowed.";
1246                         scheme = UriSchemeFile;
1247                         host = String.Empty;
1248                         port = -1;
1249                         path = uriString.Replace ("\\", "/");
1250                         fragment = String.Empty;
1251                         query = String.Empty;
1252
1253                         return null;
1254                 }
1255
1256                 private void ParseAsUnixAbsoluteFilePath (string uriString)
1257                 {
1258                         scheme = UriSchemeFile;
1259                         port = -1;
1260                         fragment = String.Empty;
1261                         query = String.Empty;
1262                         host = String.Empty;
1263                         path = null;
1264
1265                         if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1266                                 uriString = uriString.TrimStart (new char [] {'/'});
1267                                 // Now we don't regard //foo/bar as "foo" host.
1268                                 /* 
1269                                 int pos = uriString.IndexOf ('/');
1270                                 if (pos > 0) {
1271                                         path = '/' + uriString.Substring (pos + 1);
1272                                         host = uriString.Substring (0, pos);
1273                                 } else { // "///server"
1274                                         host = uriString;
1275                                         path = String.Empty;
1276                                 }
1277                                 */
1278                                 path = '/' + uriString;
1279                         }
1280                         if (path == null)
1281                                 path = uriString;
1282                 }
1283
1284                 //
1285                 // This parse method will throw exceptions on failure
1286                 //  
1287                 private void Parse (UriKind kind, string uriString)
1288                 {                       
1289                         if (uriString == null)
1290                                 throw new ArgumentNullException ("uriString");
1291
1292                         string s = ParseNoExceptions (kind, uriString);
1293                         if (s != null)
1294                                 throw new UriFormatException (s);
1295                 }
1296
1297                 private bool SupportsQuery ()
1298                 {
1299                         return UriHelper.SupportsQuery (scheme);
1300                 }
1301
1302
1303                 private string ParseNoExceptions (UriKind kind, string uriString)
1304                 {
1305                         UriElements elements;
1306                         string error;
1307                         if (!UriParseComponents.TryParseComponents (source, kind, out elements, out error))
1308                                 return error;
1309
1310                         scheme = elements.scheme;
1311                         var parser = UriParser.GetParser (scheme);
1312                         if (parser != null && !(parser is DefaultUriParser)) {
1313                                 userinfo = Parser.GetComponents (this, UriComponents.UserInfo, UriFormat.UriEscaped);
1314                                 host = Parser.GetComponents (this, UriComponents.Host, UriFormat.UriEscaped);
1315
1316                                 var portStr = Parser.GetComponents (this, UriComponents.StrongPort, UriFormat.UriEscaped);
1317                                 if (!string.IsNullOrEmpty (portStr))
1318                                         port = int.Parse (portStr);
1319
1320                                 path = Parser.GetComponents (this, UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
1321                                 query = Parser.GetComponents (this, UriComponents.Query, UriFormat.UriEscaped);
1322                                 fragment = Parser.GetComponents (this, UriComponents.StrongPort, UriFormat.UriEscaped);
1323
1324                                 return null;
1325                         }
1326
1327                         var formatFlags = UriHelper.FormatFlags.None;
1328                         if (UriHelper.HasCharactersToNormalize (uriString))
1329                                 formatFlags |= UriHelper.FormatFlags.HasUriCharactersToNormalize;
1330
1331                         if (userEscaped)
1332                                 formatFlags |= UriHelper.FormatFlags.UserEscaped;
1333
1334                         if (elements.host != null)
1335                                 formatFlags |= UriHelper.FormatFlags.HasHost;
1336
1337                         userinfo = elements.user;
1338
1339                         if (elements.host != null) {
1340                                 host = UriHelper.FormatAbsolute (elements.host, scheme,
1341                                         UriComponents.Host, UriFormat.UriEscaped, formatFlags);
1342                         }
1343
1344                         port = elements.port;
1345
1346                         if (port == -1)
1347                                 port = GetDefaultPort (scheme);
1348
1349                         if (elements.path != null) {
1350                                 path = UriHelper.FormatAbsolute (elements.path, scheme,
1351                                         UriComponents.Path, UriFormat.UriEscaped, formatFlags);
1352                                 if (elements.delimiter == SchemeDelimiter && string.IsNullOrEmpty (path))
1353                                         path = "/";
1354                         }
1355
1356                         if (elements.query != null) {
1357                                 query = "?" + UriHelper.FormatAbsolute (elements.query, scheme,
1358                                         UriComponents.Query, UriFormat.UriEscaped, formatFlags);
1359                         }
1360
1361                         if (elements.fragment != null) {
1362                                 fragment = "#" + UriHelper.FormatAbsolute (elements.fragment, scheme,
1363                                         UriComponents.Fragment, UriFormat.UriEscaped, formatFlags);
1364                         }
1365
1366                         isAbsoluteUri = elements.isAbsoluteUri;
1367                         isUnc = elements.isUnc;
1368                         scope_id = elements.scopeId;
1369
1370                         return null;
1371                 }
1372                 
1373                 private static string TryGetKnownUriSchemeInstance (string scheme)
1374                 {
1375                         foreach (string knownScheme in knownUriSchemes) {
1376                                 if (knownScheme == scheme)
1377                                         return knownScheme;
1378                         }
1379                         
1380                         return scheme;
1381                 }
1382         
1383                 private static bool CompactEscaped (string scheme)
1384                 {
1385                         if (scheme == null || scheme.Length < 4)
1386                                 return false;
1387
1388                         char first = scheme [0];
1389                         if (first == 'h'){
1390                                 return scheme == "http" || scheme == "https";
1391                         } else if (first == 'f' && scheme == "file"){
1392                                 return true;
1393                         } else if (first == 'n')
1394                                 return scheme == "net.pipe" || scheme == "net.tcp";
1395
1396                         return false;
1397                 }
1398
1399                 // replace '\', %5C ('\') and %2f ('/') into '/'
1400                 // replace %2e ('.') into '.'
1401                 private static string NormalizePath (string path)
1402                 {
1403                         StringBuilder res = new StringBuilder ();
1404                         for (int i = 0; i < path.Length; i++) {
1405                                 char c = path [i];
1406                                 switch (c) {
1407                                 case '\\':
1408                                         c = '/';
1409                                         break;
1410                                 case '%':
1411                                         if (i < path.Length - 2) {
1412                                                 char c1 = path [i + 1];
1413                                                 char c2 = Char.ToUpper (path [i + 2]);
1414                                                 if ((c1 == '2') && (c2 == 'E')) {
1415                                                         c = '.';
1416                                                         i += 2;
1417                                                 } else if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1418                                                         c = '/';
1419                                                         i += 2;
1420                                                 }
1421                                         }
1422                                         break;
1423                                 }
1424                                 res.Append (c);
1425                         }
1426                         return res.ToString ();
1427                 }
1428
1429                 // This is called "compacting" in the MSDN documentation
1430                 private static string Reduce (string path, bool compact_escaped)
1431                 {
1432                         // quick out, allocation-free, for a common case
1433                         if (path == "/")
1434                                 return path;
1435
1436                         if (compact_escaped && (path.IndexOf ('%') != -1)) {
1437                                 // replace '\', %2f, %5c with '/' and replace %2e with '.'
1438                                 // other escaped values seems to survive this step
1439                                 path = NormalizePath (path);
1440                         } else {
1441                                 // (always) replace '\' with '/'
1442                                 path = path.Replace ('\\', '/');
1443                         }
1444
1445                         List<string> result = new List<string> ();
1446
1447                         bool begin = true;
1448                         for (int startpos = 0; startpos < path.Length; ) {
1449                                 int endpos = path.IndexOf ('/', startpos);
1450                                 if (endpos == -1)
1451                                         endpos = path.Length;
1452                                 string current = path.Substring (startpos, endpos-startpos);
1453                                 startpos = endpos + 1;
1454                                 if ((begin && current.Length == 0) || current == "." ) {
1455                                         begin = false;
1456                                         continue;
1457                                 }
1458
1459                                 begin = false;
1460                                 if (current == "..") {
1461                                         int resultCount = result.Count;
1462                                         // in 2.0 profile, skip leading ".." parts
1463                                         if (resultCount == 0) {
1464                                                 continue;
1465                                         }
1466
1467                                         result.RemoveAt (resultCount - 1);
1468                                         continue;
1469                                 }
1470
1471                                 result.Add (current);
1472                         }
1473
1474                         if (result.Count == 0)
1475                                 return "/";
1476
1477                         StringBuilder res = new StringBuilder ();
1478
1479                         if (path [0] == '/')
1480                                 res.Append ('/');
1481
1482                         bool first = true;
1483                         foreach (string part in result) {
1484                                 if (first) {
1485                                         first = false;
1486                                 } else {
1487                                         res.Append ('/');
1488                                 }
1489                                 res.Append (part);
1490                         }
1491
1492                         if (path [path.Length - 1] == '/')
1493                                 res.Append ('/');
1494                                 
1495                         return res.ToString ();
1496                 }
1497
1498                 // A variant of HexUnescape() which can decode multi-byte escaped
1499                 // sequences such as (e.g.) %E3%81%8B into a single character
1500                 internal static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate) 
1501                 {
1502                         bool invalidEscape;
1503                         return HexUnescapeMultiByte (pattern, ref index, out surrogate, out invalidEscape);
1504                 }
1505
1506                 internal static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate, out bool invalidEscape)
1507                 {
1508                         surrogate = char.MinValue;
1509                         invalidEscape = false;
1510
1511                         if (pattern == null) 
1512                                 throw new ArgumentException ("pattern");
1513                                 
1514                         if (index < 0 || index >= pattern.Length)
1515                                 throw new ArgumentOutOfRangeException ("index");
1516
1517                         if (!IsHexEncoding (pattern, index))
1518                                 return pattern [index++];
1519
1520                         int orig_index = index++;
1521                         int msb = FromHex (pattern [index++]);
1522                         int lsb = FromHex (pattern [index++]);
1523
1524                         // We might be dealing with a multi-byte character:
1525                         // The number of ones at the top-end of the first byte will tell us
1526                         // how many bytes will make up this character.
1527                         int msb_copy = msb;
1528                         int num_bytes = 0;
1529                         while ((msb_copy & 0x8) == 0x8) {
1530                                 num_bytes++;
1531                                 msb_copy <<= 1;
1532                         }
1533
1534                         // We might be dealing with a single-byte character:
1535                         // If there was only 0 or 1 leading ones then we're not dealing
1536                         // with a multi-byte character.
1537                         if (num_bytes <= 1) {
1538                                 var c = (char) ((msb << 4) | lsb);
1539                                 invalidEscape = c > 0x7F;
1540                                 return c;
1541                         }
1542
1543                         // Now that we know how many bytes *should* follow, we'll check them
1544                         // to ensure we are dealing with a valid multi-byte character.
1545                         byte [] chars = new byte [num_bytes];
1546                         bool all_invalid = false;
1547                         chars[0] = (byte) ((msb << 4) | lsb);
1548
1549                         for (int i = 1; i < num_bytes; i++) {
1550                                 if (!IsHexEncoding (pattern, index++)) {
1551                                         all_invalid = true;
1552                                         break;
1553                                 }
1554
1555                                 // All following bytes must be in the form 10xxxxxx
1556                                 int cur_msb = FromHex (pattern [index++]);
1557                                 if ((cur_msb & 0xc) != 0x8) {
1558                                         all_invalid = true;
1559                                         break;
1560                                 }
1561
1562                                 int cur_lsb = FromHex (pattern [index++]);
1563                                 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1564                         }
1565
1566                         // If what looked like a multi-byte character is invalid, then we'll
1567                         // just return the first byte as a single byte character.
1568                         if (all_invalid) {
1569                                 invalidEscape = true;
1570                                 index = orig_index + 3;
1571                                 return (char) chars[0];
1572                         }
1573
1574                         // Otherwise, we're dealing with a valid multi-byte character.
1575                         // We need to ignore the leading ones from the first byte:
1576                         byte mask = (byte) 0xFF;
1577                         mask >>= (num_bytes + 1);
1578                         int result = chars[0] & mask;
1579
1580                         // The result will now be built up from the following bytes.
1581                         for (int i = 1; i < num_bytes; i++) {
1582                                 // Ignore upper two bits
1583                                 result <<= 6;
1584                                 result |= (chars[i] & 0x3F);
1585                         }
1586
1587                         if (result <= 0xFFFF) {
1588                                 return (char) result;
1589                         } else {
1590                                 // We need to handle this as a UTF16 surrogate (i.e. return
1591                                 // two characters)
1592                                 result -= 0x10000;
1593                                 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1594                                 return (char) ((result >> 10) | 0xD800);
1595                         }
1596                 }
1597
1598                 private struct UriScheme 
1599                 {
1600                         public string scheme;
1601                         public string delimiter;
1602                         public int defaultPort;
1603
1604                         public UriScheme (string s, string d, int p) 
1605                         {
1606                                 scheme = s;
1607                                 delimiter = d;
1608                                 defaultPort = p;
1609                         }
1610                 };
1611
1612                 static UriScheme [] schemes = new UriScheme [] {
1613                         new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1614                         new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1615                         new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1616                         new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1617                         new UriScheme (UriSchemeMailto, ":", 25),
1618                         new UriScheme (UriSchemeNews, ":", 119),
1619                         new UriScheme (UriSchemeUuid, ":", -1),
1620                         new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1621                         new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1622                 };
1623                                 
1624                 internal static string GetSchemeDelimiter (string scheme) 
1625                 {
1626                         for (int i = 0; i < schemes.Length; i++) 
1627                                 if (schemes [i].scheme == scheme)
1628                                         return schemes [i].delimiter;
1629                         return Uri.SchemeDelimiter;
1630                 }
1631                 
1632                 internal static int GetDefaultPort (string scheme)
1633                 {
1634                         UriParser parser = UriParser.GetParser (scheme);
1635                         if (parser == null)
1636                                 return -1;
1637                         return parser.DefaultPort;
1638                 }
1639
1640                 private string GetOpaqueWiseSchemeDelimiter ()
1641                 {
1642                         return GetSchemeDelimiter (scheme);
1643                 }
1644
1645                 [Obsolete]
1646                 protected virtual bool IsBadFileSystemCharacter (char character)
1647                 {
1648                         // It does not always overlap with InvalidPathChars.
1649                         int chInt = (int) character;
1650                         if (chInt < 32 || (chInt < 64 && chInt > 57))
1651                                 return true;
1652                         switch (chInt) {
1653                         case 0:
1654                         case 34: // "
1655                         case 38: // &
1656                         case 42: // *
1657                         case 44: // ,
1658                         case 47: // /
1659                         case 92: // \
1660                         case 94: // ^
1661                         case 124: // |
1662                                 return true;
1663                         }
1664
1665                         return false;
1666                 }
1667
1668                 [Obsolete]
1669                 protected static bool IsExcludedCharacter (char character)
1670                 {
1671                         if (character <= 32 || character >= 127)
1672                                 return true;
1673                         
1674                         if (character == '"' || character == '#' || character == '%' || character == '<' ||
1675                             character == '>' || character == '[' || character == '\\' || character == ']' ||
1676                             character == '^' || character == '`' || character == '{' || character == '|' ||
1677                             character == '}')
1678                                 return true;
1679                         return false;
1680                 }
1681
1682                 internal static bool MaybeUri (string s)
1683                 {
1684                         int p = s.IndexOf (':');
1685                         if (p == -1)
1686                                 return false;
1687
1688                         if (p >= 10)
1689                                 return false;
1690
1691                         return IsPredefinedScheme (s.Substring (0, p));
1692                 }
1693                 
1694                 //
1695                 // Using a simple block of if's is twice as slow as the compiler generated
1696                 // switch statement.   But using this tuned code is faster than the
1697                 // compiler generated code, with a million loops on x86-64:
1698                 //
1699                 // With "http": .10 vs .51 (first check)
1700                 // with "https": .16 vs .51 (second check)
1701                 // with "foo": .22 vs .31 (never found)
1702                 // with "mailto": .12 vs .51  (last check)
1703                 //
1704                 //
1705                 private static bool IsPredefinedScheme (string scheme)
1706                 {
1707                         if (scheme == null || scheme.Length < 3)
1708                                 return false;
1709                         
1710                         char c = scheme [0];
1711                         if (c == 'h')
1712                                 return (scheme == "http" || scheme == "https");
1713                         if (c == 'f')
1714                                 return (scheme == "file" || scheme == "ftp");
1715                                 
1716                         if (c == 'n'){
1717                                 c = scheme [1];
1718                                 if (c == 'e')
1719                                         return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
1720                                 if (scheme == "nntp")
1721                                         return true;
1722                                 return false;
1723                         }
1724                         if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
1725                                 return true;
1726
1727                         return false;
1728                 }
1729
1730                 [Obsolete]
1731                 protected virtual bool IsReservedCharacter (char character)
1732                 {
1733                         if (character == '$' || character == '&' || character == '+' || character == ',' ||
1734                             character == '/' || character == ':' || character == ';' || character == '=' ||
1735                             character == '@')
1736                                 return true;
1737                         return false;
1738                 }
1739
1740                 [NonSerialized]
1741                 private UriParser parser;
1742
1743                 private UriParser Parser {
1744                         get {
1745                                 if (parser == null) {
1746                                         parser = UriParser.GetParser (scheme);
1747                                         // no specific parser ? then use a default one
1748                                         if (parser == null)
1749                                                 parser = new DefaultUriParser ("*");
1750                                 }
1751                                 return parser;
1752                         }
1753                         set { parser = value; }
1754                 }
1755
1756                 public string GetComponents (UriComponents components, UriFormat format)
1757                 {
1758                         if ((components & UriComponents.SerializationInfoString) == 0)
1759                                 EnsureAbsoluteUri ();
1760
1761                         return Parser.GetComponents (this, components, format);
1762                 }
1763
1764                 public bool IsBaseOf (Uri uri)
1765                 {
1766                         if (uri == null)
1767                                 throw new ArgumentNullException ("uri");
1768                         return Parser.IsBaseOf (this, uri);
1769                 }
1770
1771                 public bool IsWellFormedOriginalString ()
1772                 {
1773                         // funny, but it does not use the Parser's IsWellFormedOriginalString().
1774                         // Also, it seems we need to *not* escape hex.
1775                         return EscapeString (OriginalString, EscapeCommonBrackets) == OriginalString;
1776                 }
1777
1778                 // static methods
1779
1780                 private const int MaxUriLength = 32766;
1781
1782                 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1783                 {
1784                         if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1785                                 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1786                                 throw new ArgumentException ("comparisonType", msg);
1787                         }
1788
1789                         if ((uri1 == null) && (uri2 == null))
1790                                 return 0;
1791                         if (uri1 == null)
1792                                 return -1;
1793                         if (uri2 == null)
1794                                 return 1;
1795
1796                         string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1797                         string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1798                         return String.Compare (s1, s2, comparisonType);
1799                 }
1800
1801                 //
1802                 // The rules for EscapeDataString
1803                 //
1804                 static bool NeedToEscapeDataChar (char b)
1805                 {
1806                         if ((b >= 'A' && b <= 'Z') ||
1807                                 (b >= 'a' && b <= 'z') ||
1808                                 (b >= '0' && b <= '9'))
1809                                 return false;
1810
1811                         switch (b) {
1812                         case '-':
1813                         case '.':
1814                         case '_':
1815                         case '~':
1816                                 return false;
1817                         }
1818
1819
1820                         return true;
1821                 }
1822                 
1823                 public static string EscapeDataString (string stringToEscape)
1824                 {
1825                         if (stringToEscape == null)
1826                                 throw new ArgumentNullException ("stringToEscape");
1827
1828                         if (stringToEscape.Length > MaxUriLength) {
1829                                 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
1830                         }
1831
1832                         bool escape = false;
1833                         foreach (char c in stringToEscape){
1834                                 if (NeedToEscapeDataChar (c)){
1835                                         escape = true;
1836                                         break;
1837                                 }
1838                         }
1839                         if (!escape){
1840                                 return stringToEscape;
1841                         }
1842                         
1843                         StringBuilder sb = new StringBuilder ();
1844                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1845                         foreach (byte b in bytes){
1846                                 if (NeedToEscapeDataChar ((char) b))
1847                                         sb.Append (HexEscape ((char) b));
1848                                 else
1849                                         sb.Append ((char) b);
1850                         }
1851                         return sb.ToString ();
1852                 }
1853
1854                 //
1855                 // The rules for EscapeUriString
1856                 //
1857                 static bool NeedToEscapeUriChar (char b)
1858                 {
1859                         if ((b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '&' && b <= ';'))
1860                                 return false;
1861
1862                         switch (b) {
1863                         case '!':
1864                         case '#':
1865                         case '$':
1866                         case '=':
1867                         case '?':
1868                         case '@':
1869                         case '_':
1870                         case '~':
1871                                 return false;
1872                         case '[':
1873                         case ']':
1874                                 return false;
1875                         }
1876
1877                         return true;
1878                 }
1879                 
1880                 public static string EscapeUriString (string stringToEscape)
1881                 {
1882                         if (stringToEscape == null)
1883                                 throw new ArgumentNullException ("stringToEscape");
1884
1885                         if (stringToEscape.Length > MaxUriLength) {
1886                                 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
1887                         }
1888
1889                         bool escape = false;
1890                         foreach (char c in stringToEscape){
1891                                 if (NeedToEscapeUriChar (c)){
1892                                         escape = true;
1893                                         break;
1894                                 }
1895                         }
1896                         if (!escape)
1897                                 return stringToEscape;
1898
1899                         StringBuilder sb = new StringBuilder ();
1900                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1901                         foreach (byte b in bytes){
1902                                 if (NeedToEscapeUriChar ((char) b))
1903                                         sb.Append (HexEscape ((char) b));
1904                                 else
1905                                         sb.Append ((char) b);
1906                         }
1907                         return sb.ToString ();
1908                 }
1909
1910                 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
1911                 {
1912                         if (uriString == null)
1913                                 return false;
1914
1915                         Uri uri;
1916                         if (Uri.TryCreate (uriString, uriKind, out uri))
1917                                 return uri.IsWellFormedOriginalString ();
1918                         return false;
1919                 }
1920
1921                 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
1922                 {
1923                         bool success;
1924
1925                         Uri r = new Uri (uriString, uriKind, out success);
1926                         if (success) {
1927                                 result = r;
1928                                 return true;
1929                         }
1930                         result = null;
1931                         return false;
1932                 }
1933
1934                 // [MonoTODO ("rework code to avoid exception catching")]
1935                 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
1936                 {
1937                         result = null;
1938                         if (relativeUri == null)
1939                                 return false;
1940
1941                         try {
1942                                 Uri relative = new Uri (relativeUri, UriKind.RelativeOrAbsolute);
1943                                 if ((baseUri != null) && baseUri.IsAbsoluteUri) {
1944                                         // FIXME: this should call UriParser.Resolve
1945                                         result = new Uri (baseUri, relative);
1946                                 } else if (relative.IsAbsoluteUri) {
1947                                         // special case - see unit tests
1948                                         result = relative;
1949                                 }
1950                                 return (result != null);
1951                         } catch (UriFormatException) {
1952                                 return false;
1953                         }
1954                 }
1955
1956                 //[MonoTODO ("rework code to avoid exception catching")]
1957                 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
1958                 {
1959                         result = null;
1960                         if ((baseUri == null) || !baseUri.IsAbsoluteUri)
1961                                 return false;
1962                         if (relativeUri == null)
1963                                 return false;
1964                         try {
1965                                 // FIXME: this should call UriParser.Resolve
1966                                 result = new Uri (baseUri, relativeUri.OriginalString);
1967                                 return true;
1968                         } catch (UriFormatException) {
1969                                 return false;
1970                         }
1971                 }
1972
1973                 public static string UnescapeDataString (string stringToUnescape)
1974                 {
1975                         return UnescapeDataString (stringToUnescape, false);
1976                 }
1977
1978                 internal static string UnescapeDataString (string stringToUnescape, bool safe)
1979                 {
1980                         if (stringToUnescape == null)
1981                                 throw new ArgumentNullException ("stringToUnescape");
1982
1983                         if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
1984                                 return stringToUnescape;
1985
1986                         StringBuilder output = new StringBuilder ();
1987                         long len = stringToUnescape.Length;
1988                         MemoryStream bytes = new MemoryStream ();
1989                         int xchar;
1990
1991                         for (int i = 0; i < len; i++) {
1992                                 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
1993                                         if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
1994                                                 if (bytes.Length > 0) {
1995                                                         output.Append (GetChars (bytes, Encoding.UTF8));
1996                                                         bytes.SetLength (0);
1997                                                 }
1998
1999                                                 xchar = GetChar (stringToUnescape, i + 2, 4, safe);
2000                                                 if (xchar != -1) {
2001                                                         output.Append ((char) xchar);
2002                                                         i += 5;
2003                                                 }
2004                                                 else {
2005                                                         output.Append ('%');
2006                                                 }
2007                                         }
2008                                         else if ((xchar = GetChar (stringToUnescape, i + 1, 2, safe)) != -1) {
2009                                                 bytes.WriteByte ((byte) xchar);
2010                                                 i += 2;
2011                                         }
2012                                         else {
2013                                                 output.Append ('%');
2014                                         }
2015                                         continue;
2016                                 }
2017
2018                                 if (bytes.Length > 0) {
2019                                         output.Append (GetChars (bytes, Encoding.UTF8));
2020                                         bytes.SetLength (0);
2021                                 }
2022
2023                                 output.Append (stringToUnescape [i]);
2024                         }
2025
2026                         if (bytes.Length > 0) {
2027                                 output.Append (GetChars (bytes, Encoding.UTF8));
2028                         }
2029
2030                         bytes = null;
2031                         return output.ToString ();
2032                 }
2033
2034                 private static int GetInt (byte b)
2035                 {
2036                         char c = (char) b;
2037                         if (c >= '0' && c <= '9')
2038                                 return c - '0';
2039
2040                         if (c >= 'a' && c <= 'f')
2041                                 return c - 'a' + 10;
2042
2043                         if (c >= 'A' && c <= 'F')
2044                                 return c - 'A' + 10;
2045
2046                         return -1;
2047                 }
2048
2049                 private static int GetChar (string str, int offset, int length, bool safe)
2050                 {
2051                         int val = 0;
2052                         int end = length + offset;
2053                         for (int i = offset; i < end; i++) {
2054                                 char c = str [i];
2055                                 if (c > 127)
2056                                         return -1;
2057
2058                                 int current = GetInt ((byte) c);
2059                                 if (current == -1)
2060                                         return -1;
2061                                 val = (val << 4) + current;
2062                         }
2063
2064                         if (!safe)
2065                                 return val;
2066
2067                         switch ((char) val) {
2068                         case '%':
2069                         case '#':
2070                         case '?':
2071                         case '/':
2072                         case '\\':
2073                         case '@':
2074                         case '&': // not documented
2075                                 return -1;
2076                         default:
2077                                 return val;
2078                         }
2079                 }
2080
2081                 private static char [] GetChars (MemoryStream b, Encoding e)
2082                 {
2083                         return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2084                 }
2085                 
2086
2087                 private void EnsureAbsoluteUri ()
2088                 {
2089                         if (!IsAbsoluteUri)
2090                                 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
2091                 }
2092         }
2093 }