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