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