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