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