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