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