Mon Dec 5 15:14:59 CET 2005 Paolo Molaro <lupus@ximian.com>
[mono.git] / mcs / class / corlib / Mono.Security / Uri.cs
1 //
2 // Mono.Security.Uri
3 //      Adapted from System.Uri (in System.dll assembly) for its use in corlib
4 //
5 // Authors:
6 //    Lawrence Pit (loz@cable.a2000.nl)
7 //    Garrett Rooney (rooneg@electricjellyfish.net)
8 //    Ian MacLean (ianm@activestate.com)
9 //    Ben Maurer (bmaurer@users.sourceforge.net)
10 //    Atsushi Enomoto (atsushi@ximian.com)
11 //
12 // (C) 2001 Garrett Rooney
13 // (C) 2003 Ian MacLean
14 // (C) 2003 Ben Maurer
15 // (C) 2003 Novell inc.
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 //
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 //
37
38 using System;
39 using System.IO;
40 using System.Text;
41 using System.Collections;
42 using System.Globalization;
43
44 // See RFC 2396 for more info on URI's.
45
46 // TODO: optimize by parsing host string only once
47
48 namespace Mono.Security {
49
50 #if INSIDE_CORLIB
51         internal
52 #else
53         public
54 #endif
55         enum UriPartial {
56                 Scheme = 0,
57                 Authority = 1,
58                 Path = 2,
59         }
60
61 #if INSIDE_CORLIB
62         internal
63 #else
64         public
65 #endif
66         class Uri {
67
68                 // NOTES:
69                 // o  scheme excludes the scheme delimiter
70                 // o  port is -1 to indicate no port is defined
71                 // o  path is empty or starts with / when scheme delimiter == "://"
72                 // o  query is empty or starts with ? char, escaped.
73                 // o  fragment is empty or starts with # char, unescaped.
74                 // o  all class variables are in escaped format when they are escapable,
75                 //    except cachedToString.
76                 // o  UNC is supported, as starts with "\\" for windows,
77                 //    or "//" with unix.
78
79                 private bool isUnixFilePath = false;
80                 private string source;
81                 private string scheme = String.Empty;
82                 private string host = String.Empty;
83                 private int port = -1;
84                 private string path = String.Empty;
85                 private string query = String.Empty;
86                 private string fragment = String.Empty;
87                 private string userinfo = String.Empty;
88                 private bool isUnc = false;
89                 private bool isOpaquePart = false;
90
91                 private string [] segments;
92                 
93                 private bool userEscaped = false;
94                 private string cachedAbsoluteUri = null;
95                 private string cachedToString = null;
96                 private string cachedLocalPath = null;
97                 private int cachedHashCode = 0;
98                 private bool reduce = true;
99                 
100                 private static readonly string hexUpperChars = "0123456789ABCDEF";
101         
102                 // Fields
103                 
104                 public static readonly string SchemeDelimiter = "://";
105                 public static readonly string UriSchemeFile = "file";
106                 public static readonly string UriSchemeFtp = "ftp";
107                 public static readonly string UriSchemeGopher = "gopher";
108                 public static readonly string UriSchemeHttp = "http";
109                 public static readonly string UriSchemeHttps = "https";
110                 public static readonly string UriSchemeMailto = "mailto";
111                 public static readonly string UriSchemeNews = "news";
112                 public static readonly string UriSchemeNntp = "nntp";
113
114                 // Constructors         
115
116                 public Uri (string uriString) : this (uriString, false) 
117                 {
118                 }
119
120                 public Uri (string uriString, bool dontEscape) 
121                 {
122                         userEscaped = dontEscape;
123                         source = uriString;
124                         Parse ();
125                 }
126
127                 public Uri (string uriString, bool dontEscape, bool reduce) 
128                 {
129                         userEscaped = dontEscape;
130                         source = uriString;
131                         this.reduce = reduce;
132                         Parse ();
133                 }
134
135                 public Uri (Uri baseUri, string relativeUri) 
136                         : this (baseUri, relativeUri, false) 
137                 {                       
138                 }
139
140                 public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
141                 {
142                         if (baseUri == null)
143                                 throw new NullReferenceException ("baseUri");
144
145                         // See RFC 2396 Par 5.2 and Appendix C
146
147                         userEscaped = dontEscape;
148
149                         if (relativeUri == null)
150                                 throw new NullReferenceException ("relativeUri");
151
152                         // Check Windows UNC (for // it is scheme/host separator)
153                         if (relativeUri.StartsWith ("\\\\")) {
154                                 source = relativeUri;
155                                 Parse ();
156                                 return;
157                         }
158
159                         int pos = relativeUri.IndexOf (':');
160                         if (pos != -1) {
161
162                                 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
163
164                                 // pos2 < 0 ... e.g. mailto
165                                 // pos2 > pos ... to block ':' in query part
166                                 if (pos2 > pos || pos2 < 0) {
167                                         // equivalent to new Uri (relativeUri, dontEscape)
168                                         source = relativeUri;
169                                         Parse ();
170
171                                         return;
172                                 }
173                         }
174
175                         this.scheme = baseUri.scheme;
176                         this.host = baseUri.host;
177                         this.port = baseUri.port;
178                         this.userinfo = baseUri.userinfo;
179                         this.isUnc = baseUri.isUnc;
180                         this.isUnixFilePath = baseUri.isUnixFilePath;
181                         this.isOpaquePart = baseUri.isOpaquePart;
182
183                         if (relativeUri == String.Empty) {
184                                 this.path = baseUri.path;
185                                 this.query = baseUri.query;
186                                 this.fragment = baseUri.fragment;
187                                 return;
188                         }
189                         
190                         // 8 fragment
191                         // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
192                         pos = relativeUri.IndexOf ('#');
193                         if (pos != -1) {
194                                 fragment = relativeUri.Substring (pos);
195                                 // fragment is not escaped.
196                                 relativeUri = relativeUri.Substring (0, pos);
197                         }
198
199                         // 6 query
200                         pos = relativeUri.IndexOf ('?');
201                         if (pos != -1) {
202                                 query = relativeUri.Substring (pos);
203                                 if (!userEscaped)
204                                         query = EscapeString (query);
205                                 relativeUri = relativeUri.Substring (0, pos);
206                         }
207
208                         if (relativeUri.Length > 0 && relativeUri [0] == '/') {
209                                 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
210                                         source = scheme + ':' + relativeUri;
211                                         Parse ();
212                                         return;
213                                 } else {
214                                         path = relativeUri;
215                                         if (!userEscaped)
216                                                 path = EscapeString (path);
217                                         return;
218                                 }
219                         }
220                         
221                         // par 5.2 step 6 a)
222                         path = baseUri.path;
223                         if (relativeUri.Length > 0 || query.Length > 0) {
224                                 pos = path.LastIndexOf ('/');
225                                 if (pos >= 0) 
226                                         path = path.Substring (0, pos + 1);
227                         }
228
229                         if(relativeUri.Length == 0)
230                                 return;
231         
232                         // 6 b)
233                         path += relativeUri;
234
235                         // 6 c)
236                         int startIndex = 0;
237                         while (true) {
238                                 pos = path.IndexOf ("./", startIndex);
239                                 if (pos == -1)
240                                         break;
241                                 if (pos == 0)
242                                         path = path.Remove (0, 2);
243                                 else if (path [pos - 1] != '.')
244                                         path = path.Remove (pos, 2);
245                                 else
246                                         startIndex = pos + 1;
247                         }
248                         
249                         // 6 d)
250                         if (path.Length > 1 && 
251                             path [path.Length - 1] == '.' &&
252                             path [path.Length - 2] == '/')
253                                 path = path.Remove (path.Length - 1, 1);
254                         
255                         // 6 e)
256                         startIndex = 0;
257                         while (true) {
258                                 pos = path.IndexOf ("/../", startIndex);
259                                 if (pos == -1)
260                                         break;
261                                 if (pos == 0) {
262                                         startIndex = 3;
263                                         continue;
264                                 }
265                                 int pos2 = path.LastIndexOf ('/', pos - 1);
266                                 if (pos2 == -1) {
267                                         startIndex = pos + 1;
268                                 } else {
269                                         if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
270                                                 path = path.Remove (pos2 + 1, pos - pos2 + 3);
271                                         else
272                                                 startIndex = pos + 1;
273                                 }
274                         }
275                         
276                         // 6 f)
277                         if (path.Length > 3 && path.EndsWith ("/..")) {
278                                 pos = path.LastIndexOf ('/', path.Length - 4);
279                                 if (pos != -1)
280                                         if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
281                                                 path = path.Remove (pos + 1, path.Length - pos - 1);
282                         }
283                         
284                         if (!userEscaped)
285                                 path = EscapeString (path);
286                 }               
287                 
288                 // Properties
289                 
290                 public string AbsolutePath { 
291                         get { return path; } 
292                 }
293
294                 public string AbsoluteUri { 
295                         get { 
296                                 if (cachedAbsoluteUri == null) {
297                                         cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
298                                 }
299                                 return cachedAbsoluteUri;
300                         } 
301                 }
302
303                 public string Authority { 
304                         get { 
305                                 return (GetDefaultPort (scheme) == port)
306                                      ? host : host + ":" + port;
307                         } 
308                 }
309
310                 public string Fragment { 
311                         get { return fragment; } 
312                 }
313
314                 public string Host { 
315                         get { return host; } 
316                 }
317
318 /*              public UriHostNameType HostNameType { 
319                         get {
320                                 UriHostNameType ret = CheckHostName (host);
321                                 if (ret != UriHostNameType.Unknown)
322                                         return ret;
323
324                                 // looks it always returns Basic...
325                                 return UriHostNameType.Basic; //.Unknown;
326                         } 
327                 }*/
328
329                 public bool IsDefaultPort { 
330                         get { return GetDefaultPort (scheme) == port; } 
331                 }
332
333                 public bool IsFile { 
334                         get { return (scheme == UriSchemeFile); }
335                 }
336
337                 public bool IsLoopback { 
338                         get { 
339                                 if (host == String.Empty)
340                                         return false;
341                                         
342                                 if (host == "loopback" || host == "localhost") 
343                                         return true;
344 /*                                      
345                                 try {
346                                         if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
347                                                 return true;
348                                 } catch (FormatException) {}
349
350                                 try {
351                                         return IPv6Address.IsLoopback (IPv6Address.Parse (host));
352                                 } catch (FormatException) {}
353 */
354                                 return false;
355                         } 
356                 }
357
358                 public bool IsUnc {
359                         // rule: This should be true only if
360                         //   - uri string starts from "\\", or
361                         //   - uri string starts from "//" (Samba way)
362                         get { return isUnc; } 
363                 }
364
365                 public string LocalPath { 
366                         get {
367                                 if (cachedLocalPath != null)
368                                         return cachedLocalPath;
369                                 if (!IsFile)
370                                         return AbsolutePath;
371
372                                 bool windows = (path.Length > 3 && path [1] == ':' &&
373                                                 (path [2] == '\\' || path [2] == '/'));
374
375                                 if (!IsUnc) {
376                                         string p = Unescape (path);
377                                         if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
378                                                 cachedLocalPath = p.Replace ('/', '\\');
379                                         else
380                                                 cachedLocalPath = p;
381                                 } else {
382                                         // support *nix and W32 styles
383                                         if (path.Length > 1 && path [1] == ':')
384                                                 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
385
386                                         // LAMESPEC: ok, now we cannot determine
387                                         // if such URI like "file://foo/bar" is
388                                         // Windows UNC or unix file path, so
389                                         // they should be handled differently.
390                                         else if (System.IO.Path.DirectorySeparatorChar == '\\')
391                                                 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
392                                         else
393                                                 cachedLocalPath = Unescape (path);
394                                 }
395                                 if (cachedLocalPath == String.Empty)
396                                         cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
397                                 return cachedLocalPath;
398                         } 
399                 }
400
401                 public string PathAndQuery { 
402                         get { return path + query; } 
403                 }
404
405                 public int Port { 
406                         get { return port; } 
407                 }
408
409                 public string Query { 
410                         get { return query; } 
411                 }
412
413                 public string Scheme { 
414                         get { return scheme; } 
415                 }
416
417                 public string [] Segments { 
418                         get { 
419                                 if (segments != null)
420                                         return segments;
421
422                                 if (path == "") {
423                                         segments = new string [0];
424                                         return segments;
425                                 }
426
427                                 string [] parts = path.Split ('/');
428                                 segments = parts;
429                                 bool endSlash = path.EndsWith ("/");
430                                 if (parts.Length > 0 && endSlash) {
431                                         string [] newParts = new string [parts.Length - 1];
432                                         Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
433                                         parts = newParts;
434                                 }
435
436                                 int i = 0;
437                                 if (IsFile && path.Length > 1 && path [1] == ':') {
438                                         string [] newParts = new string [parts.Length + 1];
439                                         Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
440                                         parts = newParts;
441                                         parts [0] = path.Substring (0, 2);
442                                         parts [1] = "";
443                                         i++;
444                                 }
445                                 
446                                 int end = parts.Length;
447                                 for (; i < end; i++) 
448                                         if (i != end - 1 || endSlash)
449                                                 parts [i] += '/';
450
451                                 segments = parts;
452                                 return segments;
453                         } 
454                 }
455
456                 public bool UserEscaped { 
457                         get { return userEscaped; } 
458                 }
459
460                 public string UserInfo { 
461                         get { return userinfo; }
462                 }
463                 
464
465                 // Methods              
466                 
467 /*              public static UriHostNameType CheckHostName (string name) 
468                 {
469                         if (name == null || name.Length == 0)
470                                 return UriHostNameType.Unknown;
471
472                         if (IsIPv4Address (name)) 
473                                 return UriHostNameType.IPv4;
474                                 
475                         if (IsDomainAddress (name))
476                                 return UriHostNameType.Dns;                             
477                                 
478                         try {
479                                 IPv6Address.Parse (name);
480                                 return UriHostNameType.IPv6;
481                         } catch (FormatException) {}
482                         
483                         return UriHostNameType.Unknown;
484                 }*/
485                 
486                 internal static bool IsIPv4Address (string name)
487                 {               
488                         string [] captures = name.Split (new char [] {'.'});
489                         if (captures.Length != 4)
490                                 return false;
491                         for (int i = 0; i < 4; i++) {
492                                 try {
493                                         int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
494                                         if (d < 0 || d > 255)
495                                                 return false;
496                                 } catch (Exception) {
497                                         return false;
498                                 }
499                         }
500                         return true;
501                 }                       
502                                 
503                 internal static bool IsDomainAddress (string name)
504                 {
505                         int len = name.Length;
506                         
507                         if (name [len - 1] == '.')
508                                 return false;
509                                 
510                         int count = 0;
511                         for (int i = 0; i < len; i++) {
512                                 char c = name [i];
513                                 if (count == 0) {
514                                         if (!Char.IsLetterOrDigit (c))
515                                                 return false;
516                                 } else if (c == '.') {
517                                         count = 0;
518                                 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
519                                         return false;
520                                 }
521                                 if (++count == 64)
522                                         return false;
523                         }
524                         
525                         return true;
526                 }
527
528 /*              [MonoTODO ("Find out what this should do")]
529                 protected virtual void Canonicalize ()
530                 {
531                 }*/
532
533                 public static bool CheckSchemeName (string schemeName) 
534                 {
535                         if (schemeName == null || schemeName.Length == 0)
536                                 return false;
537                         
538                         if (!Char.IsLetter (schemeName [0]))
539                                 return false;
540
541                         int len = schemeName.Length;
542                         for (int i = 1; i < len; i++) {
543                                 char c = schemeName [i];
544                                 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
545                                         return false;
546                         }
547                         
548                         return true;
549                 }
550
551 /*              [MonoTODO ("Find out what this should do")]
552                 protected virtual void CheckSecurity ()
553                 {
554                 }*/
555
556                 public override bool Equals (object comparant) 
557                 {
558                         if (comparant == null) 
559                                 return false;
560                                 
561                         Uri uri = comparant as Uri;
562                         if (uri == null) {
563                                 string s = comparant as String;
564                                 if (s == null)
565                                         return false;
566                                 uri = new Uri (s);
567                         }
568
569                         CultureInfo inv = CultureInfo.InvariantCulture;
570                         return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
571                                 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
572                                 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
573                                 (this.port == uri.port) &&
574                                 (this.path == uri.path) &&
575                                 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
576                 }               
577                 
578                 public override int GetHashCode () 
579                 {
580                         if (cachedHashCode == 0)                        
581                                 cachedHashCode = scheme.GetHashCode ()
582                                                + userinfo.GetHashCode ()
583                                                + host.GetHashCode ()
584                                                + port
585                                                + path.GetHashCode ()
586                                                + query.GetHashCode ();                             
587                         return cachedHashCode;                          
588                 }
589                 
590                 public string GetLeftPart (UriPartial part) 
591                 {
592                         int defaultPort;
593                         switch (part) {                         
594                         case UriPartial.Scheme : 
595                                 return scheme + GetOpaqueWiseSchemeDelimiter ();
596                         case UriPartial.Authority :
597                                 if (host == String.Empty ||
598                                     scheme == Uri.UriSchemeMailto ||
599                                     scheme == Uri.UriSchemeNews)
600                                         return String.Empty;
601                                         
602                                 StringBuilder s = new StringBuilder ();
603                                 s.Append (scheme);
604                                 s.Append (GetOpaqueWiseSchemeDelimiter ());
605                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
606                                         s.Append ('/');  // win32 file
607                                 if (userinfo.Length > 0) 
608                                         s.Append (userinfo).Append ('@');
609                                 s.Append (host);
610                                 defaultPort = GetDefaultPort (scheme);
611                                 if ((port != -1) && (port != defaultPort))
612                                         s.Append (':').Append (port);                    
613                                 return s.ToString ();                           
614                         case UriPartial.Path :                  
615                                 StringBuilder sb = new StringBuilder ();
616                                 sb.Append (scheme);
617                                 sb.Append (GetOpaqueWiseSchemeDelimiter ());
618                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
619                                         sb.Append ('/');  // win32 file
620                                 if (userinfo.Length > 0) 
621                                         sb.Append (userinfo).Append ('@');
622                                 sb.Append (host);
623                                 defaultPort = GetDefaultPort (scheme);
624                                 if ((port != -1) && (port != defaultPort))
625                                         sb.Append (':').Append (port);                   
626                                 sb.Append (path);
627                                 return sb.ToString ();
628                         }
629                         return null;
630                 }
631
632                 public static int FromHex (char digit) 
633                 {
634                         if ('0' <= digit && digit <= '9') {
635                                 return (int) (digit - '0');
636                         }
637                                 
638                         if ('a' <= digit && digit <= 'f')
639                                 return (int) (digit - 'a' + 10);
640
641                         if ('A' <= digit && digit <= 'F')
642                                 return (int) (digit - 'A' + 10);
643                                 
644                         throw new ArgumentException ("digit");
645                 }
646
647                 public static string HexEscape (char character) 
648                 {
649                         if (character > 255) {
650                                 throw new ArgumentOutOfRangeException ("character");
651                         }
652                         
653                         return "%" + hexUpperChars [((character & 0xf0) >> 4)] 
654                                    + hexUpperChars [((character & 0x0f))];
655                 }
656
657                 public static char HexUnescape (string pattern, ref int index) 
658                 {
659                         if (pattern == null) 
660                                 throw new ArgumentException ("pattern");
661                                 
662                         if (index < 0 || index >= pattern.Length)
663                                 throw new ArgumentOutOfRangeException ("index");
664
665                         int stage = 0;
666                         int c = 0;
667                         do {
668                                 if (((index + 3) > pattern.Length) ||
669                                     (pattern [index] != '%') || 
670                                     !IsHexDigit (pattern [index + 1]) || 
671                                     !IsHexDigit (pattern [index + 2]))
672                                 {
673                                         if (stage == 0)
674                                                 return pattern [index++];
675                                         break;
676                                 }
677
678                                 index++;
679                                 int msb = FromHex (pattern [index++]);
680                                 int lsb = FromHex (pattern [index++]);
681                                 int b = (msb << 4) + lsb;
682
683                                 if (stage == 0) {
684                                         if (b < 0xc0)
685                                                 return (char) b;
686                                         else if (b < 0xE0) {
687                                                 c = b - 0xc0;
688                                                 stage = 2;
689                                         } else if (b < 0xF0) {
690                                                 c = b - 0xe0;
691                                                 stage = 3;
692                                         } else if (b < 0xF8) {
693                                                 c = b - 0xf0;
694                                                 stage = 4;
695                                         } else if (b < 0xFB) {
696                                                 c = b - 0xf8;
697                                                 stage = 5;
698                                         } else if (b < 0xFE) {
699                                                 c = b - 0xfc;
700                                                 stage = 6;
701                                         }
702                                         c <<= (stage - 1) * 6;
703                                 }
704                                 else
705                                         c += (b - 0x80) << ((stage - 1) * 6);
706 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
707                                 stage--;
708                         } while (stage > 0);
709                         
710                         return (char) c;
711                 }
712
713                 public static bool IsHexDigit (char digit) 
714                 {
715                         return (('0' <= digit && digit <= '9') ||
716                                 ('a' <= digit && digit <= 'f') ||
717                                 ('A' <= digit && digit <= 'F'));
718                 }
719
720                 public static bool IsHexEncoding (string pattern, int index) 
721                 {
722                         if ((index + 3) > pattern.Length)
723                                 return false;
724
725                         return ((pattern [index++] == '%') &&
726                                 IsHexDigit (pattern [index++]) &&
727                                 IsHexDigit (pattern [index]));
728                 }
729
730                 public string MakeRelative (Uri toUri) 
731                 {
732                         if ((this.Scheme != toUri.Scheme) ||
733                             (this.Authority != toUri.Authority))
734                                 return toUri.ToString ();
735                                 
736                         if (this.path == toUri.path)
737                                 return String.Empty;
738                                 
739                         string [] segments = this.Segments;
740                         string [] segments2 = toUri.Segments;
741                         
742                         int k = 0;
743                         int max = System.Math.Min (segments.Length, segments2.Length);
744                         for (; k < max; k++)
745                                 if (segments [k] != segments2 [k]) 
746                                         break;
747                         
748                         string result = String.Empty;
749                         for (int i = k + 1; i < segments.Length; i++)
750                                 result += "../";
751                         for (int i = k; i < segments2.Length; i++)
752                                 result += segments2 [i];
753                         
754                         return result;
755                 }
756
757                 public override string ToString () 
758                 {
759                         if (cachedToString != null) 
760                                 return cachedToString;
761                         string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
762                         cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
763                         return cachedToString;
764                 }
765
766 /*              void ISerializable.GetObjectData (SerializationInfo info, 
767                                           StreamingContext context)
768                 {
769                         info.AddValue ("AbsoluteUri", this.AbsoluteUri);
770                 }*/
771
772
773                 // Internal Methods             
774
775                 protected void Escape ()
776                 {
777                         path = EscapeString (path);
778                 }
779
780                 protected static string EscapeString (string str) 
781                 {
782                         return EscapeString (str, false, true, true);
783                 }
784                 
785                 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets) 
786                 {
787                         if (str == null)
788                                 return String.Empty;
789                         
790                         byte [] data = Encoding.UTF8.GetBytes (str.ToCharArray ());
791                         StringBuilder s = new StringBuilder ();
792                         int len = data.Length;  
793                         for (int i = 0; i < len; i++) {
794                                 char c = (char) data [i];
795                                 // reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
796                                 // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
797                                 // control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
798                                 // space       = <US-ASCII coded character 20 hexadecimal>
799                                 // delims      = "<" | ">" | "#" | "%" | <">
800                                 // unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
801
802                                 // check for escape code already placed in str, 
803                                 // i.e. for encoding that follows the pattern 
804                 // "%hexhex" in a string, where "hex" is a digit from 0-9 
805                                 // or a letter from A-F (case-insensitive).
806                                 if('%'.Equals(c) && IsHexEncoding(str,i))
807                                 {
808                                         // if ,yes , copy it as is
809                                         s.Append(c);
810                                         s.Append(str[++i]);
811                                         s.Append(str[++i]);
812                                         continue;
813                                 }
814
815                                 if ((c <= 0x20) || (c >= 0x7f) || 
816                                     ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
817                                     (escapeHex && (c == '#')) ||
818                                     (escapeBrackets && (c == '[' || c == ']')) ||
819                                     (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
820                                         s.Append (HexEscape (c));
821                                         continue;
822                                 }
823                                 
824                                         
825                                 s.Append (c);
826                         }
827                         
828                         return s.ToString ();
829                 }
830
831                 // This method is called from .ctor(). When overriden, we can
832                 // avoid the "absolute uri" constraints of the .ctor() by
833                 // overriding with custom code.
834                 protected void Parse ()
835                 {
836                         Parse (source);
837
838                         if (userEscaped) 
839                                 return;
840
841                         host = EscapeString (host, false, true, false);
842                         path = EscapeString (path);
843                 }
844                 
845                 protected string Unescape (string str)
846                 {
847                         return Unescape (str, false);
848                 }
849                 
850                 internal string Unescape (string str, bool excludeSharp) 
851                 {
852                         if (str == null)
853                                 return String.Empty;
854                         StringBuilder s = new StringBuilder ();
855                         int len = str.Length;
856                         for (int i = 0; i < len; i++) {
857                                 char c = str [i];
858                                 if (c == '%') {
859                                         char x = HexUnescape (str, ref i);
860                                         if (excludeSharp && x == '#')
861                                                 s.Append ("%23");
862                                         else
863                                                 s.Append (x);
864                                         i--;
865                                 } else
866                                         s.Append (c);
867                         }
868                         return s.ToString ();
869                 }
870
871                 
872                 // Private Methods
873                 
874                 private void ParseAsWindowsUNC (string uriString)
875                 {
876                         scheme = UriSchemeFile;
877                         port = -1;
878                         fragment = String.Empty;
879                         query = String.Empty;
880                         isUnc = true;
881
882                         uriString = uriString.TrimStart (new char [] {'\\'});
883                         int pos = uriString.IndexOf ('\\');
884                         if (pos > 0) {
885                                 path = uriString.Substring (pos);
886                                 host = uriString.Substring (0, pos);
887                         } else { // "\\\\server"
888                                 host = uriString;
889                                 path = String.Empty;
890                         }
891                         path = path.Replace ("\\", "/");
892                 }
893
894                 private void ParseAsWindowsAbsoluteFilePath (string uriString)
895                 {
896                         if (uriString.Length > 2 && uriString [2] != '\\'
897                                         && uriString [2] != '/')
898                                 throw new FormatException ("Relative file path is not allowed.");
899                         scheme = UriSchemeFile;
900                         host = String.Empty;
901                         port = -1;
902                         path = uriString.Replace ("\\", "/");
903                         fragment = String.Empty;
904                         query = String.Empty;
905                 }
906
907                 private void ParseAsUnixAbsoluteFilePath (string uriString)
908                 {
909                         isUnixFilePath = true;
910                         scheme = UriSchemeFile;
911                         port = -1;
912                         fragment = String.Empty;
913                         query = String.Empty;
914                         host = String.Empty;
915                         path = null;
916
917                         if (uriString.StartsWith ("//")) {
918                                 uriString = uriString.TrimStart (new char [] {'/'});
919                                 // Now we don't regard //foo/bar as "foo" host.
920                                 /* 
921                                 int pos = uriString.IndexOf ('/');
922                                 if (pos > 0) {
923                                         path = '/' + uriString.Substring (pos + 1);
924                                         host = uriString.Substring (0, pos);
925                                 } else { // "///server"
926                                         host = uriString;
927                                         path = String.Empty;
928                                 }
929                                 */
930                                 path = '/' + uriString;
931                         }
932                         if (path == null)
933                                 path = uriString;
934                 }
935
936                 // this parse method is as relaxed as possible about the format
937                 // it will hardly ever throw a UriFormatException
938                 private void Parse (string uriString)
939                 {                       
940                         //
941                         // From RFC 2396 :
942                         //
943                         //      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
944                         //       12            3  4          5       6  7        8 9
945                         //                      
946                         
947                         if (uriString == null)
948                                 throw new ArgumentNullException ("uriString");
949
950                         int len = uriString.Length;
951                         if (len <= 1) 
952                                 throw new FormatException ();
953
954                         int pos = 0;
955
956                         // 1, 2
957                         // Identify Windows path, unix path, or standard URI.
958                         pos = uriString.IndexOf (':');
959                         if (pos < 0) {
960                                 // It must be Unix file path or Windows UNC
961                                 if (uriString [0] == '/')
962                                         ParseAsUnixAbsoluteFilePath (uriString);
963                                 else if (uriString.StartsWith ("\\\\"))
964                                         ParseAsWindowsUNC (uriString);
965                                 else
966                                         throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
967                                 return;
968                         }
969                         else if (pos == 1) {
970                                 if (!Char.IsLetter (uriString [0]))
971                                         throw new FormatException ("URI scheme must start with alphabet character.");
972                                 // This means 'a:' == windows full path.
973                                 ParseAsWindowsAbsoluteFilePath (uriString);
974                                 return;
975                         }
976
977                         // scheme
978                         scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
979                         // Check scheme name characters as specified in RFC2396.
980                         if (!Char.IsLetter (scheme [0]))
981                                         throw new FormatException ("URI scheme must start with alphabet character.");
982                         for (int i = 1; i < scheme.Length; i++) {
983                                 if (!Char.IsLetterOrDigit (scheme, i)) {
984                                         switch (scheme [i]) {
985                                         case '+':
986                                         case '-':
987                                         case '.':
988                                                 break;
989                                         default:
990                                                 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
991                                         }
992                                 }
993                         }
994                         uriString = uriString.Substring (pos + 1);
995
996                         // 8 fragment
997                         pos = uriString.IndexOf ('#');
998                         if (!IsUnc && pos != -1) {
999                                 fragment = uriString.Substring (pos);
1000                                 uriString = uriString.Substring (0, pos);
1001                         }
1002
1003                         // 6 query
1004                         pos = uriString.IndexOf ('?');
1005                         if (pos != -1) {
1006                                 query = uriString.Substring (pos);
1007                                 uriString = uriString.Substring (0, pos);
1008                                 if (!userEscaped)
1009                                         query = EscapeString (query);
1010                         }
1011
1012                         // 3
1013                         bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1014                         if (uriString.StartsWith ("//")) {
1015                                 if (uriString.StartsWith ("////"))
1016                                         unixAbsPath = false;
1017                                 uriString = uriString.TrimStart (new char [] {'/'});
1018                                 if (uriString.Length > 1 && uriString [1] == ':')
1019                                         unixAbsPath = false;
1020                         } else if (!IsPredefinedScheme (scheme)) {
1021                                 path = uriString;
1022                                 isOpaquePart = true;
1023                                 return;
1024                         }
1025                         
1026                         // 5 path
1027                         pos = uriString.IndexOfAny (new char[] {'/'});
1028                         if (unixAbsPath)
1029                                 pos = -1;
1030                         if (pos == -1) {
1031                                 if ((scheme != Uri.UriSchemeMailto) &&
1032                                     (scheme != Uri.UriSchemeNews) &&
1033                                         (scheme != Uri.UriSchemeFile))
1034                                         path = "/";
1035                         } else {
1036                                 path = uriString.Substring (pos);
1037                                 uriString = uriString.Substring (0, pos);
1038                         }
1039
1040                         // 4.a user info
1041                         pos = uriString.IndexOf ("@");
1042                         if (pos != -1) {
1043                                 userinfo = uriString.Substring (0, pos);
1044                                 uriString = uriString.Remove (0, pos + 1);
1045                         }
1046
1047                         // 4.b port
1048                         port = -1;
1049                         pos = uriString.LastIndexOf (":");
1050                         if (unixAbsPath)
1051                                 pos = -1;
1052                         if (pos != -1 && pos != (uriString.Length - 1)) {
1053                                 string portStr = uriString.Remove (0, pos + 1);
1054                                 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1055                                         try {
1056                                                 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1057                                                 uriString = uriString.Substring (0, pos);
1058                                         } catch (Exception) {
1059                                                 throw new FormatException ("Invalid URI: invalid port number");
1060                                         }
1061                                 }
1062                         }
1063                         if (port == -1) {
1064                                 port = GetDefaultPort (scheme);
1065                         }
1066                         
1067                         // 4 authority
1068                         host = uriString;
1069 /*                      if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1070                                 try {
1071                                         host = "[" + IPv6Address.Parse (host).ToString () + "]";
1072                                 } catch (Exception) {
1073                                         throw new FormatException ("Invalid URI: The hostname could not be parsed");
1074                                 }
1075                         }*/
1076
1077                         if (unixAbsPath) {
1078                                 path = '/' + uriString;
1079                                 host = String.Empty;
1080                         } else if (host.Length == 2 && host [1] == ':') {
1081                                 // windows filepath
1082                                 path = host + path;
1083                                 host = String.Empty;
1084                         } else if (isUnixFilePath) {
1085                                 uriString = "//" + uriString;
1086                                 host = String.Empty;
1087                         } else if (host.Length == 0) {
1088                                 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1089                         } else if (scheme == UriSchemeFile) {
1090                                 isUnc = true;
1091                         }
1092
1093                         if ((scheme != Uri.UriSchemeMailto) &&
1094                                         (scheme != Uri.UriSchemeNews) &&
1095                                         (scheme != Uri.UriSchemeFile))
1096
1097                         if (reduce)
1098                                 path = Reduce (path);
1099                 }
1100
1101                 private static string Reduce (string path)
1102                 {
1103                         path = path.Replace ('\\','/');
1104                         string [] parts = path.Split ('/');
1105                         ArrayList result = new ArrayList ();
1106
1107                         int end = parts.Length;
1108                         for (int i = 0; i < end; i++) {
1109                                 string current = parts [i];
1110                                 if (current == "" || current == "." )
1111                                         continue;
1112
1113                                 if (current == "..") {
1114                                         if (result.Count == 0) {
1115                                                 if (i == 1) // see bug 52599
1116                                                         continue;
1117                                                 throw new Exception ("Invalid path.");
1118                                         }
1119
1120                                         result.RemoveAt (result.Count - 1);
1121                                         continue;
1122                                 }
1123
1124                                 result.Add (current);
1125                         }
1126
1127                         if (result.Count == 0)
1128                                 return "/";
1129
1130                         result.Insert (0, "");
1131
1132                         string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1133                         if (path.EndsWith ("/"))
1134                                 res += '/';
1135                                 
1136                         return res;
1137                 }
1138                                 
1139                 private struct UriScheme 
1140                 {
1141                         public string scheme;
1142                         public string delimiter;
1143                         public int defaultPort;
1144
1145                         public UriScheme (string s, string d, int p) 
1146                         {
1147                                 scheme = s;
1148                                 delimiter = d;
1149                                 defaultPort = p;
1150                         }
1151                 };
1152
1153                 static UriScheme [] schemes = new UriScheme [] {
1154                         new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1155                         new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1156                         new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1157                         new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1158                         new UriScheme (UriSchemeMailto, ":", 25),
1159                         new UriScheme (UriSchemeNews, ":", -1),
1160                         new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1161                         new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1162                 };
1163                                 
1164                 internal static string GetSchemeDelimiter (string scheme) 
1165                 {
1166                         for (int i = 0; i < schemes.Length; i++) 
1167                                 if (schemes [i].scheme == scheme)
1168                                         return schemes [i].delimiter;
1169                         return Uri.SchemeDelimiter;
1170                 }
1171                 
1172                 internal static int GetDefaultPort (string scheme)
1173                 {
1174                         for (int i = 0; i < schemes.Length; i++) 
1175                                 if (schemes [i].scheme == scheme)
1176                                         return schemes [i].defaultPort;
1177                         return -1;                      
1178                 }
1179
1180                 private string GetOpaqueWiseSchemeDelimiter ()
1181                 {
1182                         if (isOpaquePart)
1183                                 return ":";
1184                         else
1185                                 return GetSchemeDelimiter (scheme);
1186                 }
1187
1188                 protected bool IsBadFileSystemCharacter (char ch)
1189                 {
1190                         // It does not always overlap with InvalidPathChars.
1191                         int chInt = (int) ch;
1192                         if (chInt < 32 || (chInt < 64 && chInt > 57))
1193                                 return true;
1194                         switch (chInt) {
1195                         case 0:
1196                         case 34: // "
1197                         case 38: // &
1198                         case 42: // *
1199                         case 44: // ,
1200                         case 47: // /
1201                         case 92: // \
1202                         case 94: // ^
1203                         case 124: // |
1204                                 return true;
1205                         }
1206
1207                         return false;
1208                 }
1209
1210                 
1211                 protected static bool IsExcludedCharacter (char ch)
1212                 {
1213                         if (ch <= 32 || ch >= 127)
1214                                 return true;
1215                         
1216                         if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1217                             ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1218                             ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1219                             ch == '}')
1220                                 return true;
1221                         return false;
1222                 }
1223
1224                 private static bool IsPredefinedScheme (string scheme)
1225                 {
1226                         switch (scheme) {
1227                         case "http":
1228                         case "https":
1229                         case "file":
1230                         case "ftp":
1231                         case "nntp":
1232                         case "gopher":
1233                         case "mailto":
1234                         case "news":
1235                                 return true;
1236                         default:
1237                                 return false;
1238                         }
1239                 }
1240
1241                 protected bool IsReservedCharacter (char ch)
1242                 {
1243                         if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1244                             ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1245                             ch == '@')
1246                                 return true;
1247                         return false;
1248                 }
1249         }
1250 }