8c3bb4f699bc4712879481aea3f99d6f78d3e308
[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 //    Sebastien Pouliot  <sebastien@ximian.com>
11 //
12 // (C) 2001 Garrett Rooney
13 // (C) 2003 Ian MacLean
14 // (C) 2003 Ben Maurer
15 // Copyright (C) 2003,2005 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36 // See RFC 2396 for more info on URI's.
37 //
38 // TODO: optimize by parsing host string only once
39 //
40 using System.ComponentModel;
41 using System.IO;
42 using System.Net;
43 using System.Runtime.Serialization;
44 using System.Text;
45 using System.Collections;
46 using System.Globalization;
47
48 //
49 // Disable warnings on Obsolete methods being used
50 //
51 #pragma warning disable 612
52
53 namespace System {
54
55         [Serializable]
56 #if NET_2_0
57         [TypeConverter (typeof (UriTypeConverter))]
58         public class Uri : ISerializable {
59 #else
60         public class Uri : MarshalByRefObject, ISerializable {
61 #endif
62                 // NOTES:
63                 // o  scheme excludes the scheme delimiter
64                 // o  port is -1 to indicate no port is defined
65                 // o  path is empty or starts with / when scheme delimiter == "://"
66                 // o  query is empty or starts with ? char, escaped.
67                 // o  fragment is empty or starts with # char, unescaped.
68                 // o  all class variables are in escaped format when they are escapable,
69                 //    except cachedToString.
70                 // o  UNC is supported, as starts with "\\" for windows,
71                 //    or "//" with unix.
72
73                 private bool isUnixFilePath;
74                 private string source;
75                 private string scheme = String.Empty;
76                 private string host = String.Empty;
77                 private int port = -1;
78                 private string path = String.Empty;
79                 private string query = String.Empty;
80                 private string fragment = String.Empty;
81                 private string userinfo = String.Empty;
82                 private bool isUnc;
83                 private bool isOpaquePart;
84                 private bool isAbsoluteUri = true;
85
86                 private string [] segments;
87                 
88                 private bool userEscaped;
89                 private string cachedAbsoluteUri;
90                 private string cachedToString;
91                 private string cachedLocalPath;
92                 private int cachedHashCode;
93
94                 private static readonly string hexUpperChars = "0123456789ABCDEF";
95         
96                 // Fields
97                 
98                 public static readonly string SchemeDelimiter = "://";
99                 public static readonly string UriSchemeFile = "file";
100                 public static readonly string UriSchemeFtp = "ftp";
101                 public static readonly string UriSchemeGopher = "gopher";
102                 public static readonly string UriSchemeHttp = "http";
103                 public static readonly string UriSchemeHttps = "https";
104                 public static readonly string UriSchemeMailto = "mailto";
105                 public static readonly string UriSchemeNews = "news";
106                 public static readonly string UriSchemeNntp = "nntp";
107 #if NET_2_0
108                 public static readonly string UriSchemeNetPipe = "net.pipe";
109                 public static readonly string UriSchemeNetTcp = "net.tcp";
110 #endif
111
112                 // Constructors         
113
114                 public Uri (string uriString) : this (uriString, false) 
115                 {
116                 }
117
118                 protected Uri (SerializationInfo serializationInfo, 
119                                StreamingContext streamingContext) :
120                         this (serializationInfo.GetString ("AbsoluteUri"), true)
121                 {
122                 }
123
124 #if NET_2_0
125                 public Uri (string uriString, UriKind uriKind)
126                 {
127                         source = uriString;
128                         ParseUri (uriKind);
129
130                         switch (uriKind) {
131                         case UriKind.Absolute:
132                                 if (!IsAbsoluteUri)
133                                         throw new UriFormatException("Invalid URI: The format of the URI could not be "
134                                                 + "determined.");
135                                 break;
136                         case UriKind.Relative:
137                                 if (IsAbsoluteUri)
138                                         throw new UriFormatException("Invalid URI: The format of the URI could not be "
139                                                 + "determined because the parameter 'uriString' represents an absolute URI.");
140                                 break;
141                         case UriKind.RelativeOrAbsolute:
142                                 break;
143                         default:
144                                 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
145                                 throw new ArgumentException ("uriKind", msg);
146                         }
147                 }
148
149                 public Uri (Uri baseUri, Uri relativeUri)
150                         : this (baseUri, relativeUri.OriginalString, false)
151                 {
152                         // FIXME: this should call UriParser.Resolve
153                 }
154
155                 // note: doc says that dontEscape is always false but tests show otherwise
156                 [Obsolete]
157                 public Uri (string uriString, bool dontEscape) 
158                 {
159                         userEscaped = dontEscape;
160                         source = uriString;
161                         ParseUri (UriKind.Absolute);
162                         if (!isAbsoluteUri)
163                                 throw new UriFormatException("Invalid URI: The format of the URI could not be "
164                                         + "determined: " + uriString);
165                 }
166 #else
167                 public Uri (string uriString, bool dontEscape) 
168                 {
169                         userEscaped = dontEscape;
170                         source = uriString;
171                         Parse ();
172                         if (!isAbsoluteUri)
173                                 throw new UriFormatException("Invalid URI: The format of the URI could not be "
174                                         + "determined.");
175                 }
176 #endif
177
178                 public Uri (Uri baseUri, string relativeUri) 
179                         : this (baseUri, relativeUri, false) 
180                 {
181                         // FIXME: this should call UriParser.Resolve
182                 }
183
184 #if NET_2_0
185                 [Obsolete ("dontEscape is always false")]
186 #endif
187                 public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
188                 {
189 #if NET_2_0
190                         if (baseUri == null)
191                                 throw new ArgumentNullException ("baseUri");
192                         if (relativeUri == null)
193                                 relativeUri = String.Empty;
194 #else
195                         if (baseUri == null)
196                                 throw new NullReferenceException ("baseUri");
197 #endif
198                         // See RFC 2396 Par 5.2 and Appendix C
199
200                         userEscaped = dontEscape;
201
202                         // Check Windows UNC (for // it is scheme/host separator)
203                         if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
204                                 source = relativeUri;
205 #if NET_2_0
206                                 ParseUri (UriKind.Absolute);
207 #else
208                                 Parse ();
209 #endif
210                                 return;
211                         }
212
213                         int pos = relativeUri.IndexOf (':');
214                         if (pos != -1) {
215
216                                 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
217
218                                 // pos2 < 0 ... e.g. mailto
219                                 // pos2 > pos ... to block ':' in query part
220                                 if (pos2 > pos || pos2 < 0) {
221                                         // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
222                                         // 1) when the URI scheme in the 
223                                         // relative path is different from that
224                                         // of the baseUri, or
225                                         // 2) the URI scheme is non-standard
226                                         // ones (non-standard URIs are always
227                                         // treated as absolute here), or
228                                         // 3) the relative URI path is absolute.
229                                         if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
230                                             !IsPredefinedScheme (baseUri.Scheme) ||
231                                             relativeUri.Length > pos + 1 &&
232                                             relativeUri [pos + 1] == '/') {
233                                                 source = relativeUri;
234 #if NET_2_0
235                                                 ParseUri (UriKind.Absolute);
236 #else
237                                                 Parse ();
238 #endif
239                                                 return;
240                                         }
241                                         else
242                                                 relativeUri = relativeUri.Substring (pos + 1);
243                                 }
244                         }
245
246                         this.scheme = baseUri.scheme;
247                         this.host = baseUri.host;
248                         this.port = baseUri.port;
249                         this.userinfo = baseUri.userinfo;
250                         this.isUnc = baseUri.isUnc;
251                         this.isUnixFilePath = baseUri.isUnixFilePath;
252                         this.isOpaquePart = baseUri.isOpaquePart;
253
254                         if (relativeUri == String.Empty) {
255                                 this.path = baseUri.path;
256                                 this.query = baseUri.query;
257                                 this.fragment = baseUri.fragment;
258                                 return;
259                         }
260                         
261                         // 8 fragment
262                         // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
263                         pos = relativeUri.IndexOf ('#');
264                         if (pos != -1) {
265                                 if (userEscaped)
266                                         fragment = relativeUri.Substring (pos);
267                                 else
268                                         fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
269                                 relativeUri = relativeUri.Substring (0, pos);
270                         }
271
272                         // 6 query
273                         pos = relativeUri.IndexOf ('?');
274                         if (pos != -1) {
275                                 query = relativeUri.Substring (pos);
276                                 if (!userEscaped)
277                                         query = EscapeString (query);
278                                 relativeUri = relativeUri.Substring (0, pos);
279                         }
280
281                         if (relativeUri.Length > 0 && relativeUri [0] == '/') {
282                                 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
283                                         source = scheme + ':' + relativeUri;
284 #if NET_2_0
285                                         ParseUri (UriKind.Absolute);
286 #else
287                                         Parse ();
288 #endif
289                                         return;
290                                 } else {
291                                         path = relativeUri;
292                                         if (!userEscaped)
293                                                 path = EscapeString (path);
294                                         return;
295                                 }
296                         }
297                         
298                         // par 5.2 step 6 a)
299                         path = baseUri.path;
300                         if (relativeUri.Length > 0 || query.Length > 0) {
301                                 pos = path.LastIndexOf ('/');
302                                 if (pos >= 0) 
303                                         path = path.Substring (0, pos + 1);
304                         }
305
306                         if(relativeUri.Length == 0)
307                                 return;
308         
309                         // 6 b)
310                         path += relativeUri;
311
312                         // 6 c)
313                         int startIndex = 0;
314                         while (true) {
315                                 pos = path.IndexOf ("./", startIndex);
316                                 if (pos == -1)
317                                         break;
318                                 if (pos == 0)
319                                         path = path.Remove (0, 2);
320                                 else if (path [pos - 1] != '.')
321                                         path = path.Remove (pos, 2);
322                                 else
323                                         startIndex = pos + 1;
324                         }
325                         
326                         // 6 d)
327                         if (path.Length > 1 && 
328                             path [path.Length - 1] == '.' &&
329                             path [path.Length - 2] == '/')
330                                 path = path.Remove (path.Length - 1, 1);
331                         
332                         // 6 e)
333                         startIndex = 0;
334                         while (true) {
335                                 pos = path.IndexOf ("/../", startIndex);
336                                 if (pos == -1)
337                                         break;
338                                 if (pos == 0) {
339                                         startIndex = 3;
340                                         continue;
341                                 }
342                                 int pos2 = path.LastIndexOf ('/', pos - 1);
343                                 if (pos2 == -1) {
344                                         startIndex = pos + 1;
345                                 } else {
346                                         if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
347                                                 path = path.Remove (pos2 + 1, pos - pos2 + 3);
348                                         else
349                                                 startIndex = pos + 1;
350                                 }
351                         }
352                         
353                         // 6 f)
354                         if (path.Length > 3 && path.EndsWith ("/..")) {
355                                 pos = path.LastIndexOf ('/', path.Length - 4);
356                                 if (pos != -1)
357                                         if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
358                                                 path = path.Remove (pos + 1, path.Length - pos - 1);
359                         }
360                         
361                         if (!userEscaped)
362                                 path = EscapeString (path);
363                 }               
364                 
365                 // Properties
366                 
367                 public string AbsolutePath { 
368                         get {
369 #if NET_2_0
370                                 EnsureAbsoluteUri ();
371                                 switch (Scheme) {
372                                 case "mailto":
373                                 case "file":
374                                         // faster (mailto) and special (file) cases
375                                         return path;
376                                 default:
377                                         if (path.Length == 0) {
378                                                 string start = Scheme + SchemeDelimiter;
379                                                 if (path.StartsWith (start))
380                                                         return "/";
381                                                 else
382                                                         return String.Empty;
383                                         }
384                                         return path;
385                                 }
386 #else
387                                 return path;
388 #endif
389                         }
390                 }
391
392                 public string AbsoluteUri { 
393                         get { 
394                                 EnsureAbsoluteUri ();
395                                 if (cachedAbsoluteUri == null) {
396                                         cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
397                                         if (query.Length > 0)
398                                                 cachedAbsoluteUri += query;
399                                         if (fragment.Length > 0)
400                                                 cachedAbsoluteUri += fragment;
401                                 }
402                                 return cachedAbsoluteUri;
403                         } 
404                 }
405
406                 public string Authority { 
407                         get { 
408                                 EnsureAbsoluteUri ();
409                                 return (GetDefaultPort (Scheme) == port)
410                                      ? host : host + ":" + port;
411                         } 
412                 }
413
414                 public string Fragment { 
415                         get { 
416                                 EnsureAbsoluteUri ();
417                                 return fragment; 
418                         } 
419                 }
420
421                 public string Host { 
422                         get { 
423                                 EnsureAbsoluteUri ();
424                                 return host; 
425                         } 
426                 }
427
428                 public UriHostNameType HostNameType { 
429                         get {
430                                 EnsureAbsoluteUri ();
431                                 UriHostNameType ret = CheckHostName (Host);
432                                 if (ret != UriHostNameType.Unknown)
433                                         return ret;
434 #if NET_2_0
435                                 switch (Scheme) {
436                                 case "mailto":
437                                         return UriHostNameType.Basic;
438                                 default:
439                                         return (IsFile) ? UriHostNameType.Basic : ret;
440                                 }
441 #else
442                                 // looks it always returns Basic...
443                                 return UriHostNameType.Basic; //.Unknown;
444 #endif
445                         } 
446                 }
447
448                 public bool IsDefaultPort { 
449                         get {
450                                 EnsureAbsoluteUri ();
451                                 return GetDefaultPort (Scheme) == port;
452                         }
453                 }
454
455                 public bool IsFile { 
456                         get {
457                                 EnsureAbsoluteUri ();
458                                 return (Scheme == UriSchemeFile);
459                         }
460                 }
461
462                 public bool IsLoopback { 
463                         get {
464                                 EnsureAbsoluteUri ();
465                                 
466                                 if (Host.Length == 0) {
467 #if NET_2_0
468                                         return IsFile;
469 #else
470                                         return false;
471 #endif
472                                 }
473
474                                 if (host == "loopback" || host == "localhost") 
475                                         return true;
476                                         
477                                 try {
478                                         if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
479                                                 return true;
480                                 } catch (FormatException) {}
481
482                                 try {
483                                         return IPv6Address.IsLoopback (IPv6Address.Parse (host));
484                                 } catch (FormatException) {}
485                                 
486                                 return false;
487                         } 
488                 }
489
490                 public bool IsUnc {
491                         // rule: This should be true only if
492                         //   - uri string starts from "\\", or
493                         //   - uri string starts from "//" (Samba way)
494                         get { 
495                                 EnsureAbsoluteUri ();
496                                 return isUnc; 
497                         } 
498                 }
499
500                 public string LocalPath { 
501                         get {
502                                 EnsureAbsoluteUri ();
503                                 if (cachedLocalPath != null)
504                                         return cachedLocalPath;
505                                 if (!IsFile)
506                                         return AbsolutePath;
507
508                                 bool windows = (path.Length > 3 && path [1] == ':' &&
509                                                 (path [2] == '\\' || path [2] == '/'));
510
511                                 if (!IsUnc) {
512                                         string p = Unescape (path);
513                                         bool replace = windows;
514 #if ONLY_1_1
515                                         replace |= (System.IO.Path.DirectorySeparatorChar == '\\');
516 #endif
517                                         if (replace)
518                                                 cachedLocalPath = p.Replace ('/', '\\');
519                                         else
520                                                 cachedLocalPath = p;
521                                 } else {
522                                         // support *nix and W32 styles
523                                         if (path.Length > 1 && path [1] == ':')
524                                                 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
525
526                                         // LAMESPEC: ok, now we cannot determine
527                                         // if such URI like "file://foo/bar" is
528                                         // Windows UNC or unix file path, so
529                                         // they should be handled differently.
530                                         else if (System.IO.Path.DirectorySeparatorChar == '\\') {
531                                                 string h = host;
532                                                 if (path.Length > 0) {
533 #if NET_2_0
534                                                         if ((path.Length > 1) || (path[0] != '/')) {
535                                                                 h += path.Replace ('/', '\\');
536                                                         }
537 #else
538                                                         h += path.Replace ('/', '\\');
539 #endif
540                                                 }
541                                                 cachedLocalPath = "\\\\" + Unescape (h);
542                                         }  else
543                                                 cachedLocalPath = Unescape (path);
544                                 }
545                                 if (cachedLocalPath.Length == 0)
546                                         cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
547                                 return cachedLocalPath;
548                         } 
549                 }
550
551                 public string PathAndQuery { 
552                         get {
553                                 EnsureAbsoluteUri ();
554                                 return path + Query;
555                         } 
556                 }
557
558                 public int Port { 
559                         get { 
560                                 EnsureAbsoluteUri ();
561                                 return port; 
562                         } 
563                 }
564
565                 public string Query { 
566                         get { 
567                                 EnsureAbsoluteUri ();
568                                 return query; 
569                         }
570                 }
571
572                 public string Scheme { 
573                         get { 
574                                 EnsureAbsoluteUri ();
575                                 return scheme; 
576                         } 
577                 }
578
579                 public string [] Segments { 
580                         get { 
581                                 EnsureAbsoluteUri ();
582                                 if (segments != null)
583                                         return segments;
584
585                                 if (path.Length == 0) {
586                                         segments = new string [0];
587                                         return segments;
588                                 }
589
590                                 string [] parts = path.Split ('/');
591                                 segments = parts;
592                                 bool endSlash = path.EndsWith ("/");
593                                 if (parts.Length > 0 && endSlash) {
594                                         string [] newParts = new string [parts.Length - 1];
595                                         Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
596                                         parts = newParts;
597                                 }
598
599                                 int i = 0;
600                                 if (IsFile && path.Length > 1 && path [1] == ':') {
601                                         string [] newParts = new string [parts.Length + 1];
602                                         Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
603                                         parts = newParts;
604                                         parts [0] = path.Substring (0, 2);
605                                         parts [1] = String.Empty;
606                                         i++;
607                                 }
608                                 
609                                 int end = parts.Length;
610                                 for (; i < end; i++) 
611                                         if (i != end - 1 || endSlash)
612                                                 parts [i] += '/';
613
614                                 segments = parts;
615                                 return segments;
616                         } 
617                 }
618
619                 public bool UserEscaped { 
620                         get { return userEscaped; } 
621                 }
622
623                 public string UserInfo { 
624                         get { 
625                                 EnsureAbsoluteUri ();
626                                 return userinfo; 
627                         }
628                 }
629                 
630 #if NET_2_0
631                 [MonoTODO ("add support for IPv6 address")]
632                 public string DnsSafeHost {
633                         get {
634                                 EnsureAbsoluteUri ();
635                                 return Unescape (Host);
636                         }
637                 }
638
639                 public bool IsAbsoluteUri {
640                         get { return isAbsoluteUri; }
641                 }
642
643                 // LAMESPEC: source field is supplied in such case that this
644                 // property makes sense. For such case that source field is
645                 // not supplied (i.e. .ctor(Uri, string), this property
646                 // makes no sense. To avoid silly regression it just returns
647                 // ToString() value now. See bug #78374.
648                 public string OriginalString {
649                         get { return source != null ? source : ToString (); }
650                 }
651 #endif
652
653                 // Methods              
654
655                 public static UriHostNameType CheckHostName (string name) 
656                 {
657                         if (name == null || name.Length == 0)
658                                 return UriHostNameType.Unknown;
659
660                         if (IsIPv4Address (name)) 
661                                 return UriHostNameType.IPv4;
662                                 
663                         if (IsDomainAddress (name))
664                                 return UriHostNameType.Dns;                             
665                                 
666                         try {
667                                 IPv6Address.Parse (name);
668                                 return UriHostNameType.IPv6;
669                         } catch (FormatException) {}
670                         
671                         return UriHostNameType.Unknown;
672                 }
673                 
674                 internal static bool IsIPv4Address (string name)
675                 {               
676                         string [] captures = name.Split (new char [] {'.'});
677                         if (captures.Length != 4)
678                                 return false;
679                         for (int i = 0; i < 4; i++) {
680                                 try {
681                                         int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
682                                         if (d < 0 || d > 255)
683                                                 return false;
684                                 } catch (Exception) {
685                                         return false;
686                                 }
687                         }
688                         return true;
689                 }                       
690                                 
691                 internal static bool IsDomainAddress (string name)
692                 {
693                         int len = name.Length;
694                         
695                         int count = 0;
696                         for (int i = 0; i < len; i++) {
697                                 char c = name [i];
698                                 if (count == 0) {
699                                         if (!Char.IsLetterOrDigit (c))
700                                                 return false;
701                                 } else if (c == '.') {
702                                         count = 0;
703                                 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
704                                         return false;
705                                 }
706                                 if (++count == 64)
707                                         return false;
708                         }
709                         
710                         return true;
711                 }
712
713 #if NET_2_0
714                 [Obsolete("This method does nothing, it has been obsoleted")]
715 #endif
716                 protected virtual void Canonicalize ()
717                 {
718                         //
719                         // This is flagged in the Microsoft documentation as used
720                         // internally, no longer in use, and Obsolete.
721                         //
722                 }
723
724                 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
725                 public static bool CheckSchemeName (string schemeName) 
726                 {
727                         if (schemeName == null || schemeName.Length == 0)
728                                 return false;
729                         
730                         if (!IsAlpha (schemeName [0]))
731                                 return false;
732
733                         int len = schemeName.Length;
734                         for (int i = 1; i < len; i++) {
735                                 char c = schemeName [i];
736                                 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
737                                         return false;
738                         }
739                         
740                         return true;
741                 }
742
743                 private static bool IsAlpha (char c)
744                 {
745 #if NET_2_0
746                         // as defined in rfc2234
747                         // %x41-5A / %x61-7A (A-Z / a-z)
748                         int i = (int) c;
749                         return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
750 #else
751                         // Fx 1.x got this too large
752                         return Char.IsLetter (c);
753 #endif
754                 }
755
756                 [MonoTODO ("Find out what this should do")]
757 #if NET_2_0
758                 [Obsolete]
759 #endif
760                 protected virtual void CheckSecurity ()
761                 {
762                 }
763
764                 public override bool Equals (object comparant) 
765                 {
766                         if (comparant == null) 
767                                 return false;
768
769                         Uri uri = comparant as Uri;
770                         if ((object) uri == null) {
771                                 string s = comparant as String;
772                                 if (s == null)
773                                         return false;
774                                 uri = new Uri (s);
775                         }
776
777                         return InternalEquals (uri);
778                 }
779
780                 // Assumes: uri != null
781                 bool InternalEquals (Uri uri)
782                 {
783 #if NET_2_0
784                         if (this.isAbsoluteUri != uri.isAbsoluteUri)
785                                 return false;
786                         if (!this.isAbsoluteUri)
787                                 return this.source == uri.source;
788 #endif
789
790                         CultureInfo inv = CultureInfo.InvariantCulture;
791                         return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
792                                 && this.host.ToLower (inv) == uri.host.ToLower (inv)
793                                 && this.port == uri.port
794 #if NET_2_0
795                                 && this.query == uri.query
796 #else
797                                 // Note: MS.NET 1.x has bug - ignores query check altogether
798                                 && this.query.ToLower (inv) == uri.query.ToLower (inv)
799 #endif
800                                 && this.path == uri.path;
801                 }
802
803 #if NET_2_0
804                 public static bool operator == (Uri u1, Uri u2)
805                 {
806                         return object.Equals(u1, u2);
807                 }
808
809                 public static bool operator != (Uri u1, Uri u2)
810                 {
811                         return !(u1 == u2);
812                 }
813 #endif
814
815                 public override int GetHashCode () 
816                 {
817                         if (cachedHashCode == 0) {
818                                 CultureInfo inv = CultureInfo.InvariantCulture;
819                                 if (isAbsoluteUri) {
820                                         cachedHashCode = scheme.ToLower (inv).GetHashCode ()
821                                                 ^ host.ToLower (inv).GetHashCode ()
822                                                 ^ port
823 #if NET_2_0
824                                                 ^ query.GetHashCode ()
825 #else
826                                                 ^ query.ToLower (inv).GetHashCode ()
827 #endif
828                                                 ^ path.GetHashCode ();
829                                 }
830                                 else {
831                                         cachedHashCode = source.GetHashCode ();
832                                 }
833                         }
834                         return cachedHashCode;
835                 }
836                 
837                 public string GetLeftPart (UriPartial part) 
838                 {
839                         EnsureAbsoluteUri ();
840                         int defaultPort;
841                         switch (part) {                         
842                         case UriPartial.Scheme : 
843                                 return scheme + GetOpaqueWiseSchemeDelimiter ();
844                         case UriPartial.Authority :
845                                 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
846                                         return String.Empty;
847                                         
848                                 StringBuilder s = new StringBuilder ();
849                                 s.Append (scheme);
850                                 s.Append (GetOpaqueWiseSchemeDelimiter ());
851                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
852                                         s.Append ('/');  // win32 file
853                                 if (userinfo.Length > 0) 
854                                         s.Append (userinfo).Append ('@');
855                                 s.Append (host);
856                                 defaultPort = GetDefaultPort (scheme);
857                                 if ((port != -1) && (port != defaultPort))
858                                         s.Append (':').Append (port);                    
859                                 return s.ToString ();                           
860                         case UriPartial.Path :
861                                 StringBuilder sb = new StringBuilder ();
862                                 sb.Append (scheme);
863                                 sb.Append (GetOpaqueWiseSchemeDelimiter ());
864                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
865                                         sb.Append ('/');  // win32 file
866                                 if (userinfo.Length > 0) 
867                                         sb.Append (userinfo).Append ('@');
868                                 sb.Append (host);
869                                 defaultPort = GetDefaultPort (scheme);
870                                 if ((port != -1) && (port != defaultPort))
871                                         sb.Append (':').Append (port);
872
873                                 if (path.Length > 0) {
874 #if NET_2_0
875                                         switch (Scheme) {
876                                         case "mailto":
877                                         case "news":
878                                                 sb.Append (path);
879                                                 break;
880                                         default:
881                                                 sb.Append (Reduce (path));
882                                                 break;
883                                         }
884 #else
885                                         sb.Append (path);
886 #endif
887                                 }
888                                 return sb.ToString ();
889                         }
890                         return null;
891                 }
892
893                 public static int FromHex (char digit) 
894                 {
895                         if ('0' <= digit && digit <= '9') {
896                                 return (int) (digit - '0');
897                         }
898                                 
899                         if ('a' <= digit && digit <= 'f')
900                                 return (int) (digit - 'a' + 10);
901
902                         if ('A' <= digit && digit <= 'F')
903                                 return (int) (digit - 'A' + 10);
904                                 
905                         throw new ArgumentException ("digit");
906                 }
907
908                 public static string HexEscape (char character) 
909                 {
910                         if (character > 255) {
911                                 throw new ArgumentOutOfRangeException ("character");
912                         }
913                         
914                         return "%" + hexUpperChars [((character & 0xf0) >> 4)] 
915                                    + hexUpperChars [((character & 0x0f))];
916                 }
917
918                 public static char HexUnescape (string pattern, ref int index) 
919                 {
920                         if (pattern == null) 
921                                 throw new ArgumentException ("pattern");
922                                 
923                         if (index < 0 || index >= pattern.Length)
924                                 throw new ArgumentOutOfRangeException ("index");
925
926                         if (!IsHexEncoding (pattern, index))
927                                 return pattern [index++];
928
929                         index++;
930                         int msb = FromHex (pattern [index++]);
931                         int lsb = FromHex (pattern [index++]);
932                         return (char) ((msb << 4) | lsb);
933                 }
934
935                 public static bool IsHexDigit (char digit) 
936                 {
937                         return (('0' <= digit && digit <= '9') ||
938                                 ('a' <= digit && digit <= 'f') ||
939                                 ('A' <= digit && digit <= 'F'));
940                 }
941
942                 public static bool IsHexEncoding (string pattern, int index) 
943                 {
944                         if ((index + 3) > pattern.Length)
945                                 return false;
946
947                         return ((pattern [index++] == '%') &&
948                                 IsHexDigit (pattern [index++]) &&
949                                 IsHexDigit (pattern [index]));
950                 }
951
952 #if NET_2_0
953                 //
954                 // Implemented by copying most of the MakeRelative code
955                 //
956                 public Uri MakeRelativeUri (Uri uri)
957                 {
958                         if (uri == null)
959                                 throw new ArgumentNullException ("uri");
960
961                         if (Host != uri.Host || Scheme != uri.Scheme)
962                                 return uri;
963
964                         string result = String.Empty;
965                         if (this.path != uri.path){
966                                 string [] segments = this.Segments;
967                                 string [] segments2 = uri.Segments;
968                                 
969                                 int k = 0;
970                                 int max = Math.Min (segments.Length, segments2.Length);
971                                 for (; k < max; k++)
972                                         if (segments [k] != segments2 [k]) 
973                                                 break;
974                                 
975                                 for (int i = k + 1; i < segments.Length; i++)
976                                         result += "../";
977                                 for (int i = k; i < segments2.Length; i++)
978                                         result += segments2 [i];
979                                 
980                         }
981                         uri.AppendQueryAndFragment (ref result);
982
983                         return new Uri (result, UriKind.Relative);
984                 }
985
986                 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
987 #endif
988                 public string MakeRelative (Uri toUri) 
989                 {
990                         if ((this.Scheme != toUri.Scheme) ||
991                             (this.Authority != toUri.Authority))
992                                 return toUri.ToString ();
993
994                         string result = String.Empty;
995                         if (this.path != toUri.path){
996                                 string [] segments = this.Segments;
997                                 string [] segments2 = toUri.Segments;
998                                 int k = 0;
999                                 int max = Math.Min (segments.Length, segments2.Length);
1000                                 for (; k < max; k++)
1001                                         if (segments [k] != segments2 [k]) 
1002                                                 break;
1003                                 
1004                                 for (int i = k + 1; i < segments.Length; i++)
1005                                         result += "../";
1006                                 for (int i = k; i < segments2.Length; i++)
1007                                         result += segments2 [i];
1008                         }
1009
1010                         // Important: MakeRelative does not append fragment or query.
1011
1012                         return result;
1013                 }
1014
1015                 void AppendQueryAndFragment (ref string result)
1016                 {
1017                         if (query.Length > 0) {
1018                                 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), false) : Unescape (query, false);
1019                                 result += q;
1020                         }
1021                         if (fragment.Length > 0)
1022                                 result += fragment;
1023                 }
1024                 
1025                 public override string ToString () 
1026                 {
1027                         if (cachedToString != null) 
1028                                 return cachedToString;
1029
1030                         if (isAbsoluteUri)
1031                                 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1032                         else {
1033                                 // Everything is contained in path in this case. 
1034                                 cachedToString = Unescape (path);
1035                         }
1036
1037                         AppendQueryAndFragment (ref cachedToString);
1038                         return cachedToString;
1039                 }
1040
1041 #if NET_2_0
1042                 protected void GetObjectData (SerializationInfo info, StreamingContext context)
1043                 {
1044                         info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1045                 }
1046 #endif
1047
1048                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1049                 {
1050                         info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1051                 }
1052
1053
1054                 // Internal Methods             
1055
1056 #if NET_2_0
1057                 [Obsolete]
1058 #endif
1059                 protected virtual void Escape ()
1060                 {
1061                         path = EscapeString (path);
1062                 }
1063
1064 #if NET_2_0
1065                 [Obsolete]
1066 #endif
1067                 protected static string EscapeString (string str) 
1068                 {
1069                         return EscapeString (str, false, true, true);
1070                 }
1071                 
1072                 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets) 
1073                 {
1074                         if (str == null)
1075                                 return String.Empty;
1076                         
1077                         byte [] data = Encoding.UTF8.GetBytes (str);
1078                         StringBuilder s = new StringBuilder ();
1079                         int len = data.Length;  
1080                         for (int i = 0; i < len; i++) {
1081                                 char c = (char) data [i];
1082                                 // reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1083                                 // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1084                                 // control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1085                                 // space       = <US-ASCII coded character 20 hexadecimal>
1086                                 // delims      = "<" | ">" | "#" | "%" | <">
1087                                 // unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1088
1089                                 // check for escape code already placed in str, 
1090                                 // i.e. for encoding that follows the pattern 
1091                                 // "%hexhex" in a string, where "hex" is a digit from 0-9 
1092                                 // or a letter from A-F (case-insensitive).
1093                                 if('%' == c && IsHexEncoding(str,i))
1094                                 {
1095                                         // if ,yes , copy it as is
1096                                         s.Append(c);
1097                                         s.Append(str[++i]);
1098                                         s.Append(str[++i]);
1099                                         continue;
1100                                 }
1101
1102                                 if ((c <= 0x20) || (c >= 0x7f) || 
1103                                     ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
1104                                     (escapeHex && (c == '#')) ||
1105                                     (escapeBrackets && (c == '[' || c == ']')) ||
1106                                     (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
1107                                         s.Append (HexEscape (c));
1108                                         continue;
1109                                 }
1110                                 
1111                                         
1112                                 s.Append (c);
1113                         }
1114                         
1115                         return s.ToString ();
1116                 }
1117
1118                 // On .NET 1.x, this method is called from .ctor(). When overriden, we 
1119                 // can avoid the "absolute uri" constraints of the .ctor() by
1120                 // overriding with custom code.
1121 #if NET_2_0
1122                 [Obsolete("The method has been deprecated. It is not used by the system.")]
1123 #endif
1124                 protected virtual void Parse ()
1125                 {
1126 #if !NET_2_0
1127                         ParseUri (UriKind.Absolute);
1128 #endif
1129                 }
1130
1131                 private void ParseUri (UriKind kind)
1132                 {
1133                         Parse (kind, source);
1134
1135                         if (userEscaped)
1136                                 return;
1137
1138                         host = EscapeString (host, false, true, false);
1139                         if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1140                                 // host name present (but not an IPv6 address)
1141                                 host = host.ToLower (CultureInfo.InvariantCulture);
1142                         }
1143
1144                         if (path.Length > 0) {
1145                                 path = EscapeString (path);
1146                         }
1147                 }
1148
1149 #if NET_2_0
1150                 [Obsolete]
1151 #endif
1152                 protected virtual string Unescape (string str)
1153                 {
1154                         return Unescape (str, false);
1155                 }
1156                 
1157                 internal static string Unescape (string str, bool excludeSpecial) 
1158                 {
1159                         if (str == null)
1160                                 return String.Empty;
1161                         StringBuilder s = new StringBuilder ();
1162                         int len = str.Length;
1163                         for (int i = 0; i < len; i++) {
1164                                 char c = str [i];
1165                                 if (c == '%') {
1166                                         char surrogate;
1167                                         char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1168                                         if (excludeSpecial && x == '#')
1169                                                 s.Append ("%23");
1170                                         else if (excludeSpecial && x == '%')
1171                                                 s.Append ("%25");
1172                                         else if (excludeSpecial && x == '?')
1173                                                 s.Append ("%3F");
1174                                         else {
1175                                                 s.Append (x);
1176                                                 if (surrogate != char.MinValue)
1177                                                         s.Append (surrogate);
1178                                         }
1179                                         i--;
1180                                 } else
1181                                         s.Append (c);
1182                         }
1183                         return s.ToString ();
1184                 }
1185
1186                 
1187                 // Private Methods
1188                 
1189                 private void ParseAsWindowsUNC (string uriString)
1190                 {
1191                         scheme = UriSchemeFile;
1192                         port = -1;
1193                         fragment = String.Empty;
1194                         query = String.Empty;
1195                         isUnc = true;
1196
1197                         uriString = uriString.TrimStart (new char [] {'\\'});
1198                         int pos = uriString.IndexOf ('\\');
1199                         if (pos > 0) {
1200                                 path = uriString.Substring (pos);
1201                                 host = uriString.Substring (0, pos);
1202                         } else { // "\\\\server"
1203                                 host = uriString;
1204                                 path = String.Empty;
1205                         }
1206                         path = path.Replace ("\\", "/");
1207                 }
1208
1209                 private void ParseAsWindowsAbsoluteFilePath (string uriString)
1210                 {
1211                         if (uriString.Length > 2 && uriString [2] != '\\'
1212                                         && uriString [2] != '/')
1213                                 throw new UriFormatException ("Relative file path is not allowed.");
1214                         scheme = UriSchemeFile;
1215                         host = String.Empty;
1216                         port = -1;
1217                         path = uriString.Replace ("\\", "/");
1218                         fragment = String.Empty;
1219                         query = String.Empty;
1220                 }
1221
1222                 private void ParseAsUnixAbsoluteFilePath (string uriString)
1223                 {
1224                         isUnixFilePath = true;
1225                         scheme = UriSchemeFile;
1226                         port = -1;
1227                         fragment = String.Empty;
1228                         query = String.Empty;
1229                         host = String.Empty;
1230                         path = null;
1231
1232                         if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1233                                 uriString = uriString.TrimStart (new char [] {'/'});
1234                                 // Now we don't regard //foo/bar as "foo" host.
1235                                 /* 
1236                                 int pos = uriString.IndexOf ('/');
1237                                 if (pos > 0) {
1238                                         path = '/' + uriString.Substring (pos + 1);
1239                                         host = uriString.Substring (0, pos);
1240                                 } else { // "///server"
1241                                         host = uriString;
1242                                         path = String.Empty;
1243                                 }
1244                                 */
1245                                 path = '/' + uriString;
1246                         }
1247                         if (path == null)
1248                                 path = uriString;
1249                 }
1250
1251                 // this parse method is as relaxed as possible about the format
1252                 // it will hardly ever throw a UriFormatException
1253                 private void Parse (UriKind kind, string uriString)
1254                 {                       
1255                         //
1256                         // From RFC 2396 :
1257                         //
1258                         //      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1259                         //       12            3  4          5       6  7        8 9
1260                         //                      
1261                         
1262                         if (uriString == null)
1263                                 throw new ArgumentNullException ("uriString");
1264
1265                         uriString = uriString.Trim();
1266                         int len = uriString.Length;
1267
1268                         if (len == 0){
1269                                 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1270                                         isAbsoluteUri = false;
1271                                         return;
1272                                 }
1273                         }
1274                         
1275                         if (len <= 1) 
1276                                 throw new UriFormatException ();
1277
1278                         int pos = 0;
1279
1280                         // 1, 2
1281                         // Identify Windows path, unix path, or standard URI.
1282                         pos = uriString.IndexOf (':');
1283                         if (pos == 0) {
1284                                 throw new UriFormatException("Invalid URI: The format of the URI could not be determined.");
1285                         } else if (pos < 0) {
1286                                 // It must be Unix file path or Windows UNC
1287                                 if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1288                                         ParseAsUnixAbsoluteFilePath (uriString);
1289                                         if (kind == UriKind.Relative)
1290                                                 isAbsoluteUri = false;
1291                                         
1292                                 } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\')
1293                                         ParseAsWindowsUNC (uriString);
1294                                 else {
1295                                         /* Relative path */
1296                                         isAbsoluteUri = false;
1297                                         path = uriString;
1298                                 }
1299                                 return;
1300                         } else if (pos == 1) {
1301                                 if (!IsAlpha (uriString [0]))
1302                                         throw new UriFormatException ("URI scheme must start with a letter.");
1303                                 // This means 'a:' == windows full path.
1304                                 ParseAsWindowsAbsoluteFilePath (uriString);
1305                                 return;
1306                         } 
1307
1308                         // scheme
1309                         scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1310
1311                         // Check scheme name characters as specified in RFC2396.
1312                         // Note: different checks in 1.x and 2.0
1313                         if (!CheckSchemeName (scheme)) {
1314                                 string msg = Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
1315                                 throw new UriFormatException (msg);
1316                         }
1317
1318                         // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1319                         int startpos = pos + 1;
1320                         int endpos = uriString.Length;
1321
1322                         // 8 fragment
1323                         pos = uriString.IndexOf ('#', startpos);
1324                         if (!IsUnc && pos != -1) {
1325                                 if (userEscaped)
1326                                         fragment = uriString.Substring (pos);
1327                                 else
1328                                         fragment = "#" + EscapeString (uriString.Substring (pos+1));
1329
1330                                 endpos = pos;
1331                         }
1332
1333                         // 6 query
1334                         pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1335                         if (pos != -1) {
1336                                 query = uriString.Substring (pos, endpos-pos);
1337                                 endpos = pos;
1338                                 if (!userEscaped)
1339                                         query = EscapeString (query);
1340                         }
1341
1342                         // 3
1343                         if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
1344                                 (endpos-startpos < 2) ||
1345                                 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))                                
1346                                 throw new UriFormatException ("Invalid URI: The Authority/Host could not be parsed.");
1347                         
1348                         
1349                         bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1350                         bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1351                         if (startsWithSlashSlash) {
1352                                 if (kind == UriKind.Relative)
1353                                         throw new UriFormatException ("Absolute URI when we expected a relative one");
1354                                 
1355                                 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1356                                         startpos += 2;
1357
1358                                 if (scheme == UriSchemeFile) {
1359                                         int num_leading_slash = 2;
1360                                         for (int i = startpos; i < endpos; i++) {
1361                                                 if (uriString [i] != '/')
1362                                                         break;
1363                                                 num_leading_slash++;
1364                                         }
1365                                         if (num_leading_slash >= 4) {
1366                                                 unixAbsPath = false;
1367                                                 while (startpos < endpos && uriString[startpos] == '/') {
1368                                                         startpos++;
1369                                                 }
1370                                         } else if (num_leading_slash >= 3) {
1371                                                 startpos += 1;
1372                                         }
1373                                 }
1374                                 
1375                                 if (endpos - startpos > 1 && uriString [startpos + 1] == ':')
1376                                         unixAbsPath = false;
1377
1378                         } else if (!IsPredefinedScheme (scheme)) {
1379                                 path = uriString.Substring(startpos, endpos-startpos);
1380                                 isOpaquePart = true;
1381                                 return;
1382                         }
1383
1384                         // 5 path
1385                         pos = uriString.IndexOf ('/', startpos, endpos-startpos);
1386                         if (unixAbsPath)
1387                                 pos = -1;
1388                         if (pos == -1) {
1389                                 if ((scheme != Uri.UriSchemeMailto) &&
1390 #if ONLY_1_1
1391                                     (scheme != Uri.UriSchemeFile) &&
1392 #endif
1393                                     (scheme != Uri.UriSchemeNews))
1394                                         path = "/";
1395                         } else {
1396                                 path = uriString.Substring (pos, endpos-pos);
1397                                 endpos = pos;
1398                         }
1399
1400                         // 4.a user info
1401                         pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1402                         if (pos != -1) {
1403                                 userinfo = uriString.Substring (startpos, pos-startpos);
1404                                 startpos = pos + 1;
1405                         }
1406
1407                         // 4.b port
1408                         port = -1;
1409                         pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1410                         if (unixAbsPath)
1411                                 pos = -1;
1412                         if (pos != -1 && pos != endpos - 1) {
1413                                 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1414                                 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1415                                         try {
1416 #if NET_2_0
1417                                                 port = (int) UInt16.Parse (portStr, NumberStyles.Integer, CultureInfo.InvariantCulture);
1418 #else
1419                                                 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1420 #endif
1421                                                 endpos = pos;
1422                                         } catch (Exception) {
1423                                                 throw new UriFormatException ("Invalid URI: Invalid port number");
1424                                         }
1425                                 } else {
1426                                         if (port == -1) {
1427                                                 port = GetDefaultPort (scheme);
1428                                         }
1429                                 }
1430                         } else {
1431                                 if (port == -1) {
1432                                         port = GetDefaultPort (scheme);
1433                                 }
1434                         }
1435                         
1436                         // 4 authority
1437                         uriString = uriString.Substring(startpos, endpos-startpos);
1438                         host = uriString;
1439
1440                         if (unixAbsPath) {
1441                                 path = '/' + uriString;
1442                                 host = String.Empty;
1443                         } else if (host.Length == 2 && host [1] == ':') {
1444                                 // windows filepath
1445                                 path = host + path;
1446                                 host = String.Empty;
1447                         } else if (isUnixFilePath) {
1448                                 uriString = "//" + uriString;
1449                                 host = String.Empty;
1450                         } else if (scheme == UriSchemeFile) {
1451                                 isUnc = true;
1452                         } else if (scheme == UriSchemeNews) {
1453                                 // no host for 'news', misinterpreted path
1454                                 if (host.Length > 0) {
1455                                         path = host;
1456                                         host = String.Empty;
1457                                 }
1458                         } else if (host.Length == 0 &&
1459                                 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp
1460                                  || scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1461                                 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1462                         }
1463
1464                         bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
1465                         if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
1466                                 try {
1467                                         host = "[" + IPv6Address.Parse (host).ToString (true) + "]";
1468                                 }
1469                                 catch (Exception) {
1470                                         badhost = true;
1471                                 }
1472                         }
1473                         if (badhost) {
1474                                 string msg = Locale.GetText ("Invalid URI: The hostname could not be parsed.");
1475                                 throw new UriFormatException (msg);
1476                         }
1477
1478                         if ((scheme != Uri.UriSchemeMailto) &&
1479                                         (scheme != Uri.UriSchemeNews) &&
1480                                         (scheme != Uri.UriSchemeFile)) {
1481                                 path = Reduce (path);
1482                         }
1483                 }
1484
1485                 private static string Reduce (string path)
1486                 {
1487                         path = path.Replace ('\\','/');
1488                         ArrayList result = new ArrayList ();
1489
1490                         for (int startpos = 0; startpos < path.Length; ) {
1491                                 int endpos = path.IndexOf('/', startpos);
1492                                 if (endpos == -1) endpos = path.Length;
1493                                 string current = path.Substring (startpos, endpos-startpos);
1494                                 startpos = endpos + 1;
1495                                 if (current.Length == 0 || current == "." )
1496                                         continue;
1497
1498                                 if (current == "..") {
1499                                         int resultCount = result.Count;
1500 #if NET_2_0
1501                                         // in 2.0 profile, skip leading ".." parts
1502                                         if (resultCount == 0) {
1503                                                 continue;
1504                                         }
1505
1506                                         result.RemoveAt (resultCount - 1);
1507                                         continue;
1508 #else
1509                                         // in 1.x profile, retain leading ".." parts, and only reduce
1510                                         // URI is previous part is not ".."
1511                                         if (resultCount > 0) {
1512                                                 if ((string) result[resultCount - 1] != "..") {
1513                                                         result.RemoveAt (resultCount - 1);
1514                                                         continue;
1515                                                 }
1516                                         }
1517 #endif
1518                                 }
1519
1520                                 result.Add (current);
1521                         }
1522
1523                         if (result.Count == 0)
1524                                 return "/";
1525
1526                         StringBuilder res = new StringBuilder();
1527                         if (path [0] == '/')
1528                                 res.Append ('/');
1529
1530                         bool first = true;
1531                         foreach (string part in result) {
1532                                 if (first) {
1533                                         first = false;
1534                                 } else {
1535                                         res.Append ('/');
1536                                 }
1537                                 res.Append(part);
1538                         }
1539
1540                         if (path.EndsWith ("/"))
1541                                 res.Append ('/');
1542                                 
1543                         return res.ToString();
1544                 }
1545
1546                 // A variant of HexUnescape() which can decode multi-byte escaped
1547                 // sequences such as (e.g.) %E3%81%8B into a single character
1548                 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate) 
1549                 {
1550                         surrogate = char.MinValue;
1551
1552                         if (pattern == null) 
1553                                 throw new ArgumentException ("pattern");
1554                                 
1555                         if (index < 0 || index >= pattern.Length)
1556                                 throw new ArgumentOutOfRangeException ("index");
1557
1558                         if (!IsHexEncoding (pattern, index))
1559                                 return pattern [index++];
1560
1561                         int orig_index = index++;
1562                         int msb = FromHex (pattern [index++]);
1563                         int lsb = FromHex (pattern [index++]);
1564
1565                         // We might be dealing with a multi-byte character:
1566                         // The number of ones at the top-end of the first byte will tell us
1567                         // how many bytes will make up this character.
1568                         int msb_copy = msb;
1569                         int num_bytes = 0;
1570                         while ((msb_copy & 0x8) == 0x8) {
1571                                 num_bytes++;
1572                                 msb_copy <<= 1;
1573                         }
1574
1575                         // We might be dealing with a single-byte character:
1576                         // If there was only 0 or 1 leading ones then we're not dealing
1577                         // with a multi-byte character.
1578                         if (num_bytes <= 1)
1579                                 return (char) ((msb << 4) | lsb);
1580
1581                         // Now that we know how many bytes *should* follow, we'll check them
1582                         // to ensure we are dealing with a valid multi-byte character.
1583                         byte [] chars = new byte [num_bytes];
1584                         bool all_invalid = false;
1585                         chars[0] = (byte) ((msb << 4) | lsb);
1586
1587                         for (int i = 1; i < num_bytes; i++) {
1588                                 if (!IsHexEncoding (pattern, index++)) {
1589                                         all_invalid = true;
1590                                         break;
1591                                 }
1592
1593                                 // All following bytes must be in the form 10xxxxxx
1594                                 int cur_msb = FromHex (pattern [index++]);
1595                                 if ((cur_msb & 0xc) != 0x8) {
1596                                         all_invalid = true;
1597                                         break;
1598                                 }
1599
1600                                 int cur_lsb = FromHex (pattern [index++]);
1601                                 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1602                         }
1603
1604                         // If what looked like a multi-byte character is invalid, then we'll
1605                         // just return the first byte as a single byte character.
1606                         if (all_invalid) {
1607                                 index = orig_index + 3;
1608                                 return (char) chars[0];
1609                         }
1610
1611                         // Otherwise, we're dealing with a valid multi-byte character.
1612                         // We need to ignore the leading ones from the first byte:
1613                         byte mask = (byte) 0xFF;
1614                         mask >>= (num_bytes + 1);
1615                         int result = chars[0] & mask;
1616
1617                         // The result will now be built up from the following bytes.
1618                         for (int i = 1; i < num_bytes; i++) {
1619                                 // Ignore upper two bits
1620                                 result <<= 6;
1621                                 result |= (chars[i] & 0x3F);
1622                         }
1623
1624                         if (result <= 0xFFFF) {
1625                                 return (char) result;
1626                         } else {
1627                                 // We need to handle this as a UTF16 surrogate (i.e. return
1628                                 // two characters)
1629                                 result -= 0x10000;
1630                                 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1631                                 return (char) ((result >> 10) | 0xD800);
1632                         }
1633                 }
1634
1635                 private struct UriScheme 
1636                 {
1637                         public string scheme;
1638                         public string delimiter;
1639                         public int defaultPort;
1640
1641                         public UriScheme (string s, string d, int p) 
1642                         {
1643                                 scheme = s;
1644                                 delimiter = d;
1645                                 defaultPort = p;
1646                         }
1647                 };
1648
1649                 static UriScheme [] schemes = new UriScheme [] {
1650                         new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1651                         new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1652                         new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1653                         new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1654                         new UriScheme (UriSchemeMailto, ":", 25),
1655                         new UriScheme (UriSchemeNews, ":", 119),
1656                         new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1657                         new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1658                 };
1659                                 
1660                 internal static string GetSchemeDelimiter (string scheme) 
1661                 {
1662                         for (int i = 0; i < schemes.Length; i++) 
1663                                 if (schemes [i].scheme == scheme)
1664                                         return schemes [i].delimiter;
1665                         return Uri.SchemeDelimiter;
1666                 }
1667                 
1668                 internal static int GetDefaultPort (string scheme)
1669                 {
1670 #if NET_2_0
1671                         UriParser parser = UriParser.GetParser (scheme);
1672                         if (parser == null)
1673                                 return -1;
1674                         return parser.DefaultPort;
1675 #else
1676                         for (int i = 0; i < schemes.Length; i++) 
1677                                 if (schemes [i].scheme == scheme)
1678                                         return schemes [i].defaultPort;
1679                         return -1;
1680 #endif
1681                 }
1682
1683                 private string GetOpaqueWiseSchemeDelimiter ()
1684                 {
1685                         if (isOpaquePart)
1686                                 return ":";
1687                         else
1688                                 return GetSchemeDelimiter (scheme);
1689                 }
1690
1691 #if NET_2_0
1692                 [Obsolete]
1693 #endif
1694                 protected virtual bool IsBadFileSystemCharacter (char ch)
1695                 {
1696                         // It does not always overlap with InvalidPathChars.
1697                         int chInt = (int) ch;
1698                         if (chInt < 32 || (chInt < 64 && chInt > 57))
1699                                 return true;
1700                         switch (chInt) {
1701                         case 0:
1702                         case 34: // "
1703                         case 38: // &
1704                         case 42: // *
1705                         case 44: // ,
1706                         case 47: // /
1707                         case 92: // \
1708                         case 94: // ^
1709                         case 124: // |
1710                                 return true;
1711                         }
1712
1713                         return false;
1714                 }
1715
1716 #if NET_2_0
1717                 [Obsolete]
1718 #endif
1719                 protected static bool IsExcludedCharacter (char ch)
1720                 {
1721                         if (ch <= 32 || ch >= 127)
1722                                 return true;
1723                         
1724                         if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1725                             ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1726                             ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1727                             ch == '}')
1728                                 return true;
1729                         return false;
1730                 }
1731
1732                 private static bool IsPredefinedScheme (string scheme)
1733                 {
1734                         switch (scheme) {
1735                         case "http":
1736                         case "https":
1737                         case "file":
1738                         case "ftp":
1739                         case "nntp":
1740                         case "gopher":
1741                         case "mailto":
1742                         case "news":
1743 #if NET_2_0
1744                         case "net.pipe":
1745                         case "net.tcp":
1746 #endif
1747                                 return true;
1748                         default:
1749                                 return false;
1750                         }
1751                 }
1752
1753 #if NET_2_0
1754                 [Obsolete]
1755 #endif
1756                 protected virtual bool IsReservedCharacter (char ch)
1757                 {
1758                         if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1759                             ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1760                             ch == '@')
1761                                 return true;
1762                         return false;
1763                 }
1764 #if NET_2_0
1765                 private UriParser parser;
1766
1767                 private UriParser Parser {
1768                         get {
1769                                 if (parser == null)
1770                                         parser = UriParser.GetParser (Scheme);
1771                                 return parser;
1772                         }
1773                         set { parser = value; }
1774                 }
1775
1776                 public string GetComponents (UriComponents components, UriFormat format)
1777                 {
1778                         return Parser.GetComponents (this, components, format);
1779                 }
1780
1781                 public bool IsBaseOf (Uri uri)
1782                 {
1783                         return Parser.IsBaseOf (this, uri);
1784                 }
1785
1786                 public bool IsWellFormedOriginalString ()
1787                 {
1788                         // funny, but it does not use the Parser's IsWellFormedOriginalString().
1789                         return EscapeString (OriginalString) == OriginalString;
1790                 }
1791
1792                 // static methods
1793
1794                 private const int MaxUriLength = 32766;
1795
1796                 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1797                 {
1798                         if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1799                                 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1800                                 throw new ArgumentException ("comparisonType", msg);
1801                         }
1802
1803                         if ((uri1 == null) && (uri2 == null))
1804                                 return 0;
1805
1806                         string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1807                         string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1808                         return String.Compare (s1, s2, comparisonType);
1809                 }
1810
1811                 //
1812                 // The rules for EscapeDataString
1813                 //
1814                 static bool NeedToEscapeDataChar (char b)
1815                 {
1816                         return !((b >= 'A' && b <= 'Z') ||
1817                                  (b >= 'a' && b <= 'z') ||
1818                                  (b >= '0' && b <= '9') ||
1819                                  b == '_' || b == '~' || b == '!' || b == '\'' ||
1820                                  b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
1821                 }
1822                 
1823                 public static string EscapeDataString (string stringToEscape)
1824                 {
1825                         if (stringToEscape == null)
1826                                 throw new ArgumentNullException ("stringToEscape");
1827
1828                         if (stringToEscape.Length > MaxUriLength) {
1829                                 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1830                                 throw new UriFormatException (msg);
1831                         }
1832                         bool escape = false;
1833                         foreach (char c in stringToEscape){
1834                                 if (NeedToEscapeDataChar (c)){
1835                                         escape = true;
1836                                         break;
1837                                 }
1838                         }
1839                         if (!escape){
1840                                 return stringToEscape;
1841                         }
1842                         
1843                         StringBuilder sb = new StringBuilder ();
1844                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1845                         foreach (byte b in bytes){
1846                                 if (NeedToEscapeDataChar ((char) b))
1847                                         sb.Append (HexEscape ((char) b));
1848                                 else
1849                                         sb.Append ((char) b);
1850                         }
1851                         return sb.ToString ();
1852                 }
1853
1854                 //
1855                 // The rules for EscapeUriString
1856                 //
1857                 static bool NeedToEscapeUriChar (char b)
1858                 {
1859                         return !((b >= 'A' && b <= 'Z') ||
1860                                  (b >= 'a' && b <= 'z') ||
1861                                  (b >= '&' && b <= ';') ||
1862                                  b == '!' || b == '#' || b == '$' || b == '=' ||
1863                                  b == '?' || b == '@' || b == '_' || b == '~');
1864                 }
1865                 
1866                 public static string EscapeUriString (string stringToEscape)
1867                 {
1868                         if (stringToEscape == null)
1869                                 throw new ArgumentNullException ("stringToEscape");
1870
1871                         if (stringToEscape.Length > MaxUriLength) {
1872                                 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1873                                 throw new UriFormatException (msg);
1874                         }
1875
1876                         bool escape = false;
1877                         foreach (char c in stringToEscape){
1878                                 if (NeedToEscapeUriChar (c)){
1879                                         escape = true;
1880                                         break;
1881                                 }
1882                         }
1883                         if (!escape)
1884                                 return stringToEscape;
1885
1886                         StringBuilder sb = new StringBuilder ();
1887                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1888                         foreach (byte b in bytes){
1889                                 if (NeedToEscapeUriChar ((char) b))
1890                                         sb.Append (HexEscape ((char) b));
1891                                 else
1892                                         sb.Append ((char) b);
1893                         }
1894                         return sb.ToString ();
1895                 }
1896
1897                 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
1898                 {
1899                         if (uriString == null)
1900                                 return false;
1901                         Uri uri = new Uri (uriString, uriKind);
1902                         return uri.IsWellFormedOriginalString ();
1903                 }
1904
1905                 // [MonoTODO ("rework code to avoid exception catching")]
1906                 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
1907                 {
1908                         try {
1909                                 result = new Uri (uriString, uriKind);
1910                                 return true;
1911                         }
1912                         catch (UriFormatException) {
1913                                 result = null;
1914                                 return false;
1915                         }
1916                 }
1917
1918                 // [MonoTODO ("rework code to avoid exception catching")]
1919                 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
1920                 {
1921                         try {
1922                                 // FIXME: this should call UriParser.Resolve
1923                                 result = new Uri (baseUri, relativeUri);
1924                                 return true;
1925                         }
1926                         catch (UriFormatException) {
1927                                 result = null;
1928                                 return false;
1929                         }
1930                 }
1931
1932                 //[MonoTODO ("rework code to avoid exception catching")]
1933                 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
1934                 {
1935                         try {
1936                                 // FIXME: this should call UriParser.Resolve
1937                                 result = new Uri (baseUri, relativeUri);
1938                                 return true;
1939                         }
1940                         catch (UriFormatException) {
1941                                 result = null;
1942                                 return false;
1943                         }
1944                 }
1945
1946                 public static string UnescapeDataString (string stringToUnescape)
1947                 {
1948                         if (stringToUnescape == null)
1949                                 throw new ArgumentNullException ("stringToUnescape");
1950
1951                         if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
1952                                 return stringToUnescape;
1953
1954                         StringBuilder output = new StringBuilder ();
1955                         long len = stringToUnescape.Length;
1956                         MemoryStream bytes = new MemoryStream ();
1957                         int xchar;
1958
1959                         for (int i = 0; i < len; i++) {
1960                                 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
1961                                         if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
1962                                                 if (bytes.Length > 0) {
1963                                                         output.Append (GetChars (bytes, Encoding.UTF8));
1964                                                         bytes.SetLength (0);
1965                                                 }
1966
1967                                                 xchar = GetChar (stringToUnescape, i + 2, 4);
1968                                                 if (xchar != -1) {
1969                                                         output.Append ((char) xchar);
1970                                                         i += 5;
1971                                                 }
1972                                                 else {
1973                                                         output.Append ('%');
1974                                                 }
1975                                         }
1976                                         else if ((xchar = GetChar (stringToUnescape, i + 1, 2)) != -1) {
1977                                                 bytes.WriteByte ((byte) xchar);
1978                                                 i += 2;
1979                                         }
1980                                         else {
1981                                                 output.Append ('%');
1982                                         }
1983                                         continue;
1984                                 }
1985
1986                                 if (bytes.Length > 0) {
1987                                         output.Append (GetChars (bytes, Encoding.UTF8));
1988                                         bytes.SetLength (0);
1989                                 }
1990
1991                                 output.Append (stringToUnescape [i]);
1992                         }
1993
1994                         if (bytes.Length > 0) {
1995                                 output.Append (GetChars (bytes, Encoding.UTF8));
1996                         }
1997
1998                         bytes = null;
1999                         return output.ToString ();
2000                 }
2001
2002                 private static int GetInt (byte b)
2003                 {
2004                         char c = (char) b;
2005                         if (c >= '0' && c <= '9')
2006                                 return c - '0';
2007
2008                         if (c >= 'a' && c <= 'f')
2009                                 return c - 'a' + 10;
2010
2011                         if (c >= 'A' && c <= 'F')
2012                                 return c - 'A' + 10;
2013
2014                         return -1;
2015                 }
2016
2017                 private static int GetChar (string str, int offset, int length)
2018                 {
2019                         int val = 0;
2020                         int end = length + offset;
2021                         for (int i = offset; i < end; i++) {
2022                                 char c = str [i];
2023                                 if (c > 127)
2024                                         return -1;
2025
2026                                 int current = GetInt ((byte) c);
2027                                 if (current == -1)
2028                                         return -1;
2029                                 val = (val << 4) + current;
2030                         }
2031
2032                         return val;
2033                 }
2034
2035                 private static char [] GetChars (MemoryStream b, Encoding e)
2036                 {
2037                         return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2038                 }
2039                 
2040
2041                 private void EnsureAbsoluteUri ()
2042                 {
2043                         if (!IsAbsoluteUri)
2044                                 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
2045                 }
2046 #else
2047                 private void EnsureAbsoluteUri ()
2048                 {
2049                 }
2050 #endif
2051         }
2052 }