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