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