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