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