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