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