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