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