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