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