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