2004-01-05 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System / System / Uri.cs
1 //
2 // System.Uri
3 //
4 // Authors:
5 //    Lawrence Pit (loz@cable.a2000.nl)
6 //    Garrett Rooney (rooneg@electricjellyfish.net)
7 //    Ian MacLean (ianm@activestate.com)
8 //    Ben Maurer (bmaurer@users.sourceforge.net)
9 //    Atsushi Enomoto (atsushi@ximian.com)
10 //
11 // (C) 2001 Garrett Rooney
12 // (C) 2003 Ian MacLean
13 // (C) 2003 Ben Maurer
14 // (C) 2003 Novell inc.
15 //
16
17 using System.Net;
18 using System.Runtime.Serialization;
19 using System.Text;
20
21 // See RFC 2396 for more info on URI's.
22
23 // TODO: optimize by parsing host string only once
24
25 namespace System 
26 {
27         [Serializable]
28         public class Uri : MarshalByRefObject, ISerializable 
29         {
30                 // NOTES:
31                 // o  scheme excludes the scheme delimiter
32                 // o  port is -1 to indicate no port is defined
33                 // o  path is empty or starts with / when scheme delimiter == "://"
34                 // o  query is empty or starts with ? char
35                 // o  fragment is empty or starts with # char
36                 // o  all class variables are in escaped format when they are escapable,
37                 //    except cachedToString.
38                 // o  UNC is supported, as starts with "\\" for windows,
39                 //    or "//" with unix.
40
41                 private bool isWindowsFilePath = false;
42                 private bool isUnixFilePath = false;
43                 private string scheme = String.Empty;
44                 private string host = String.Empty;
45                 private int port = -1;
46                 private string path = String.Empty;
47                 private string query = String.Empty;
48                 private string fragment = String.Empty;
49                 private string userinfo = String.Empty;
50                 private bool is_root_path = false;
51                 private bool is_wins_dir = true;
52                 private bool isUnc = false;
53                 private bool isOpaquePart = false;
54
55                 private string [] segments;
56                 
57                 private bool userEscaped = false;
58                 private string cachedAbsoluteUri = null;
59                 private string cachedToString = null;
60                 private int cachedHashCode = 0;
61                 
62                 private static readonly string hexUpperChars = "0123456789ABCDEF";
63         
64                 // Fields
65                 
66                 public static readonly string SchemeDelimiter = "://";
67                 public static readonly string UriSchemeFile = "file";
68                 public static readonly string UriSchemeFtp = "ftp";
69                 public static readonly string UriSchemeGopher = "gopher";
70                 public static readonly string UriSchemeHttp = "http";
71                 public static readonly string UriSchemeHttps = "https";
72                 public static readonly string UriSchemeMailto = "mailto";
73                 public static readonly string UriSchemeNews = "news";
74                 public static readonly string UriSchemeNntp = "nntp";
75
76                 // Constructors         
77
78                 public Uri (string uriString) : this (uriString, false) 
79                 {
80                 }
81
82                 protected Uri (SerializationInfo serializationInfo, 
83                                StreamingContext streamingContext) :
84                         this (serializationInfo.GetString ("AbsoluteUri"), true)
85                 {
86                 }
87
88                 public Uri (string uriString, bool dontEscape) 
89                 {                       
90                         userEscaped = dontEscape;
91                         Parse (uriString);
92                         
93                         if (userEscaped) 
94                                 return;
95
96                         host = EscapeString (host, false, true, false);
97                         path = EscapeString (path);
98                         query = EscapeString (query);
99                         fragment = EscapeString (fragment, false, false, true);
100                 }
101
102                 public Uri (Uri baseUri, string relativeUri) 
103                         : this (baseUri, relativeUri, false) 
104                 {                       
105                 }
106
107                 public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
108                 {
109                         if (baseUri == null)
110                                 throw new NullReferenceException ("baseUri");
111
112                         // See RFC 2396 Par 5.2 and Appendix C
113
114                         userEscaped = dontEscape;
115
116                         this.scheme = baseUri.scheme;
117                         this.host = baseUri.host;
118                         this.port = baseUri.port;
119                         this.userinfo = baseUri.userinfo;
120                         this.isUnc = baseUri.isUnc;
121                         this.isWindowsFilePath = baseUri.isWindowsFilePath;
122                         this.isUnixFilePath = baseUri.isUnixFilePath;
123                         this.isOpaquePart = baseUri.isOpaquePart;
124
125                         if (relativeUri == null)
126                                 throw new NullReferenceException ("relativeUri");
127
128                         if (relativeUri == String.Empty) {
129                                 this.path = baseUri.path;
130                                 this.query = baseUri.query;
131                                 this.fragment = baseUri.fragment;
132                                 return;
133                         }
134
135                         int pos = relativeUri.IndexOf (':');
136                         if (pos != -1) {
137
138                                 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\'});
139
140                                 if (pos2 > pos) {
141                                         // equivalent to new Uri (relativeUri, dontEscape)
142                                         Parse (relativeUri);
143
144                                         if (userEscaped) 
145                                                 return;
146
147                                         host = EscapeString (host, false, true, false);
148                                         path = EscapeString (path);
149                                         query = EscapeString (query);
150                                         fragment = EscapeString (fragment, false, false, true);
151                                         return;
152                                 }
153                         }
154
155                         // 8 fragment
156                         pos = relativeUri.IndexOf ('#');
157                         if (pos != -1) {
158                                 fragment = relativeUri.Substring (pos);
159                                 if (!userEscaped)
160                                         fragment = EscapeString (fragment, false, false, true);
161                                 relativeUri = relativeUri.Substring (0, pos);
162                         }
163
164                         // 6 query
165                         pos = relativeUri.IndexOf ('?');
166                         if (pos != -1) {
167                                 query = relativeUri.Substring (pos);
168                                 if (!userEscaped)
169                                         query = EscapeString (query);
170                                 relativeUri = relativeUri.Substring (0, pos);
171                         }
172
173                         if (relativeUri.Length > 0 && relativeUri [0] == '/') {
174                                 path = relativeUri;
175                                 if (!userEscaped)
176                                         path = EscapeString (path);
177                                 return;
178                         }
179                         
180                         // par 5.2 step 6 a)
181                         path = baseUri.path;
182                         if (relativeUri.Length > 0 || query.Length > 0) {
183                                 pos = path.LastIndexOf ('/');
184                                 if (pos >= 0) 
185                                         path = path.Substring (0, pos + 1);
186                         }
187
188                         if(relativeUri.Length == 0)
189                                 return;
190         
191                         // 6 b)
192                         path += relativeUri;
193
194                         // 6 c)
195                         int startIndex = 0;
196                         while (true) {
197                                 pos = path.IndexOf ("./", startIndex);
198                                 if (pos == -1)
199                                         break;
200                                 if (pos == 0)
201                                         path = path.Remove (0, 2);
202                                 else if (path [pos - 1] != '.')
203                                         path = path.Remove (pos, 2);
204                                 else
205                                         startIndex = pos + 1;
206                         }
207                         
208                         // 6 d)
209                         if (path.Length > 1 && 
210                             path [path.Length - 1] == '.' &&
211                             path [path.Length - 2] == '/')
212                                 path = path.Remove (path.Length - 1, 1);
213                         
214                         // 6 e)
215                         startIndex = 0;
216                         while (true) {
217                                 pos = path.IndexOf ("/../", startIndex);
218                                 if (pos == -1)
219                                         break;
220                                 if (pos == 0) {
221                                         startIndex = 3;
222                                         continue;
223                                 }
224                                 int pos2 = path.LastIndexOf ('/', pos - 1);
225                                 if (pos2 == -1) {
226                                         startIndex = pos + 1;
227                                 } else {
228                                         if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
229                                                 path = path.Remove (pos2 + 1, pos - pos2 + 3);
230                                         else
231                                                 startIndex = pos + 1;
232                                 }
233                         }
234                         
235                         // 6 f)
236                         if (path.Length > 3 && path.EndsWith ("/..")) {
237                                 pos = path.LastIndexOf ('/', path.Length - 4);
238                                 if (pos != -1)
239                                         if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
240                                                 path = path.Remove (pos + 1, path.Length - pos - 1);
241                         }
242                         
243                         if (!userEscaped)
244                                 path = EscapeString (path);
245                 }               
246                 
247                 // Properties
248                 
249                 public string AbsolutePath { 
250                         get { return path; } 
251                 }
252
253                 public string AbsoluteUri { 
254                         get { 
255                                 if (cachedAbsoluteUri == null)
256                                         cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
257                                 return cachedAbsoluteUri;
258                         } 
259                 }
260
261                 public string Authority { 
262                         get { 
263                                 return (GetDefaultPort (scheme) == port)
264                                      ? host : host + ":" + port;
265                         } 
266                 }
267
268                 public string Fragment { 
269                         get { return fragment; } 
270                 }
271
272                 public string Host { 
273                         get { return host; } 
274                 }
275
276                 public UriHostNameType HostNameType { 
277                         get {
278                                 UriHostNameType ret = CheckHostName (host);
279                                 if (ret != UriHostNameType.Unknown)
280                                         return ret;
281
282                                 // looks it always returns Basic...
283                                 return UriHostNameType.Basic; //.Unknown;
284                         } 
285                 }
286
287                 public bool IsDefaultPort { 
288                         get { return GetDefaultPort (scheme) == port; } 
289                 }
290
291                 public bool IsFile { 
292                         get { return (scheme == UriSchemeFile); }
293                 }
294
295                 public bool IsLoopback { 
296                         get { 
297                                 if (host == String.Empty)
298                                         return false;
299                                         
300                                 if (host == "loopback" || host == "localhost") 
301                                         return true;
302                                         
303                                 try {
304                                         return IPAddress.IsLoopback (IPAddress.Parse (host));
305                                 } catch (FormatException) {}
306
307                                 try {
308                                         return IPv6Address.IsLoopback (IPv6Address.Parse (host));
309                                 } catch (FormatException) {}
310                                 
311                                 return false;
312                         } 
313                 }
314
315                 public bool IsUnc {
316                         // rule: This should be true only if
317                         //   - uri string starts from "\\", or
318                         //   - uri string starts from "//" (Samba way)
319                         get { return isUnc; } 
320                 }
321
322                 public string LocalPath { 
323                         get {
324                                 if (!IsFile)
325                                         return path;
326                                 if (!IsUnc) {
327                                         if (System.IO.Path.DirectorySeparatorChar == '\\')
328                                                 return path.Replace ('/', '\\');
329                                         else
330                                                 return path;
331                                 }
332
333                                 // support *nix and W32 styles
334                                 if (path.Length > 1 && path [1] == ':')
335                                         return Unescape (path.Replace ('/', '\\'));
336
337                                 if (System.IO.Path.DirectorySeparatorChar == '\\')
338                                         return "\\\\" + Unescape (host + path.Replace ('/', '\\'));
339                                 else 
340                                         return (is_root_path? "/": "") + (is_wins_dir? "/": "") + Unescape (host + path);
341                         } 
342                 }
343
344                 public string PathAndQuery { 
345                         get { return path + query; } 
346                 }
347
348                 public int Port { 
349                         get { return port; } 
350                 }
351
352                 public string Query { 
353                         get { return query; } 
354                 }
355
356                 public string Scheme { 
357                         get { return scheme; } 
358                 }
359
360                 public string [] Segments { 
361                         get { 
362                                 if (segments != null)
363                                         return segments;
364
365                                 if (path == "") {
366                                         segments = new string [0];
367                                         return segments;
368                                 }
369
370                                 string [] parts = path.Split ('/');
371                                 segments = parts;
372                                 bool endSlash = path.EndsWith ("/");
373                                 if (parts.Length > 0 && endSlash) {
374                                         string [] newParts = new string [parts.Length - 1];
375                                         Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
376                                         parts = newParts;
377                                 }
378
379                                 int i = 0;
380                                 if (IsFile && path.Length > 1 && path [1] == ':') {
381                                         string [] newParts = new string [parts.Length + 1];
382                                         Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
383                                         parts = newParts;
384                                         parts [0] = path.Substring (0, 2);
385                                         parts [1] = "";
386                                         i++;
387                                 }
388                                 
389                                 int end = parts.Length;
390                                 for (; i < end; i++) 
391                                         if (i != end - 1 || endSlash)
392                                                 parts [i] += '/';
393
394                                 segments = parts;
395                                 return segments;
396                         } 
397                 }
398
399                 public bool UserEscaped { 
400                         get { return userEscaped; } 
401                 }
402
403                 public string UserInfo { 
404                         get { return userinfo; }
405                 }
406                 
407
408                 // Methods              
409                 
410                 public static UriHostNameType CheckHostName (string name) 
411                 {
412                         if (name == null || name.Length == 0)
413                                 return UriHostNameType.Unknown;
414
415                         if (IsIPv4Address (name)) 
416                                 return UriHostNameType.IPv4;
417                                 
418                         if (IsDomainAddress (name))
419                                 return UriHostNameType.Dns;                             
420                                 
421                         try {
422                                 IPv6Address.Parse (name);
423                                 return UriHostNameType.IPv6;
424                         } catch (FormatException) {}
425                         
426                         return UriHostNameType.Unknown;
427                 }
428                 
429                 internal static bool IsIPv4Address (string name)
430                 {               
431                         string [] captures = name.Split (new char [] {'.'});
432                         if (captures.Length != 4)
433                                 return false;
434                         for (int i = 0; i < 4; i++) {
435                                 try {
436                                         int d = Int32.Parse (captures [i]);
437                                         if (d < 0 || d > 255)
438                                                 return false;
439                                 } catch (Exception) {
440                                         return false;
441                                 }
442                         }
443                         return true;
444                 }                       
445                                 
446                 internal static bool IsDomainAddress (string name)
447                 {
448                         int len = name.Length;
449                         
450                         if (name [len - 1] == '.')
451                                 return false;
452                                 
453                         int count = 0;
454                         for (int i = 0; i < len; i++) {
455                                 char c = name [i];
456                                 if (count == 0) {
457                                         if (!Char.IsLetterOrDigit (c))
458                                                 return false;
459                                 } else if (c == '.') {
460                                         count = 0;
461                                 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
462                                         return false;
463                                 }
464                                 if (++count == 64)
465                                         return false;
466                         }
467                         
468                         return true;
469                 }
470
471                 [MonoTODO ("Find out what this should do")]
472                 protected virtual void Canonicalize ()
473                 {
474                 }
475
476                 public static bool CheckSchemeName (string schemeName) 
477                 {
478                         if (schemeName == null || schemeName.Length == 0)
479                                 return false;
480                         
481                         if (!Char.IsLetter (schemeName [0]))
482                                 return false;
483
484                         int len = schemeName.Length;
485                         for (int i = 1; i < len; i++) {
486                                 char c = schemeName [i];
487                                 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
488                                         return false;
489                         }
490                         
491                         return true;
492                 }
493
494                 [MonoTODO ("Find out what this should do")]
495                 protected virtual void CheckSecurity ()
496                 {
497                 }
498
499                 public override bool Equals (object comparant) 
500                 {
501                         if (comparant == null) 
502                                 return false;
503                                 
504                         Uri uri = comparant as Uri;
505                         if (uri == null) {
506                                 string s = comparant as String;
507                                 if (s == null)
508                                         return false;
509                                 uri = new Uri (s);
510                         }
511                         
512                         return ((this.scheme == uri.scheme) &&
513                                 (this.userinfo == uri.userinfo) &&
514                                 (this.host == uri.host) &&
515                                 (this.port == uri.port) &&
516                                 (this.path == uri.path) &&
517                                 (this.query == uri.query));
518                 }               
519                 
520                 public override int GetHashCode () 
521                 {
522                         if (cachedHashCode == 0)                        
523                                 cachedHashCode = scheme.GetHashCode ()
524                                                + userinfo.GetHashCode ()
525                                                + host.GetHashCode ()
526                                                + port
527                                                + path.GetHashCode ()
528                                                + query.GetHashCode ();                             
529                         return cachedHashCode;                          
530                 }
531                 
532                 public string GetLeftPart (UriPartial part) 
533                 {
534                         int defaultPort;
535                         switch (part) {                         
536                         case UriPartial.Scheme : 
537                                 return scheme + GetOpaqueWiseSchemeDelimiter ();
538                         case UriPartial.Authority :
539                                 if (host == String.Empty ||
540                                     scheme == Uri.UriSchemeMailto ||
541                                     scheme == Uri.UriSchemeNews)
542                                         return String.Empty;
543                                         
544                                 StringBuilder s = new StringBuilder ();
545                                 s.Append (scheme);
546                                 s.Append (GetOpaqueWiseSchemeDelimiter ());
547                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
548                                         s.Append ('/');  // win32 file
549                                 if (userinfo.Length > 0) 
550                                         s.Append (userinfo).Append ('@');
551                                 s.Append (host);
552                                 defaultPort = GetDefaultPort (scheme);
553                                 if ((port != -1) && (port != defaultPort))
554                                         s.Append (':').Append (port);                    
555                                 return s.ToString ();                           
556                         case UriPartial.Path :                  
557                                 StringBuilder sb = new StringBuilder ();
558                                 sb.Append (scheme);
559                                 sb.Append (GetOpaqueWiseSchemeDelimiter ());
560                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
561                                         sb.Append ('/');  // win32 file
562                                 if (userinfo.Length > 0) 
563                                         sb.Append (userinfo).Append ('@');
564                                 sb.Append (host);
565                                 defaultPort = GetDefaultPort (scheme);
566                                 if ((port != -1) && (port != defaultPort))
567                                         sb.Append (':').Append (port);                   
568                                 sb.Append (path);
569                                 return sb.ToString ();
570                         }
571                         return null;
572                 }
573
574                 public static int FromHex (char digit) 
575                 {
576                         if ('0' <= digit && digit <= '9') {
577                                 return (int) (digit - '0');
578                         }
579                                 
580                         if ('a' <= digit && digit <= 'f')
581                                 return (int) (digit - 'a' + 10);
582
583                         if ('A' <= digit && digit <= 'F')
584                                 return (int) (digit - 'A' + 10);
585                                 
586                         throw new ArgumentException ("digit");
587                 }
588
589                 public static string HexEscape (char character) 
590                 {
591                         if (character > 255) {
592                                 throw new ArgumentOutOfRangeException ("character");
593                         }
594                         
595                         return "%" + hexUpperChars [((character & 0xf0) >> 4)] 
596                                    + hexUpperChars [((character & 0x0f))];
597                 }
598
599                 public static char HexUnescape (string pattern, ref int index) 
600                 {
601                         if (pattern == null) 
602                                 throw new ArgumentException ("pattern");
603                                 
604                         if (index < 0 || index >= pattern.Length)
605                                 throw new ArgumentOutOfRangeException ("index");        
606                                 
607                         if (((index + 3) > pattern.Length) ||
608                             (pattern [index] != '%') || 
609                             !IsHexDigit (pattern [index + 1]) || 
610                             !IsHexDigit (pattern [index + 2]))
611                         {
612                                 return pattern[index++];
613                         }
614                         
615                         index++;
616                         return (char) ((FromHex (pattern [index++]) << 4) + FromHex (pattern [index++]));
617                 }
618
619                 public static bool IsHexDigit (char digit) 
620                 {
621                         return (('0' <= digit && digit <= '9') ||
622                                 ('a' <= digit && digit <= 'f') ||
623                                 ('A' <= digit && digit <= 'F'));
624                 }
625
626                 public static bool IsHexEncoding (string pattern, int index) 
627                 {
628                         if ((index + 3) > pattern.Length)
629                                 return false;
630
631                         return ((pattern [index++] == '%') &&
632                                 IsHexDigit (pattern [index++]) &&
633                                 IsHexDigit (pattern [index]));
634                 }
635
636                 public string MakeRelative (Uri toUri) 
637                 {
638                         if ((this.Scheme != toUri.Scheme) ||
639                             (this.Authority != toUri.Authority))
640                                 return toUri.ToString ();
641                                 
642                         if (this.path == toUri.path)
643                                 return String.Empty;
644                                 
645                         string [] segments = this.Segments;
646                         string [] segments2 = toUri.Segments;
647                         
648                         int k = 0;
649                         int max = Math.Min (segments.Length, segments2.Length);
650                         for (; k < max; k++)
651                                 if (segments [k] != segments2 [k]) 
652                                         break;
653                         
654                         string result = String.Empty;
655                         for (int i = k + 1; i < segments.Length; i++)
656                                 result += "../";
657                         for (int i = k; i < segments2.Length; i++)
658                                 result += segments2 [i];
659                         
660                         return result;
661                 }
662
663                 public override string ToString () 
664                 {
665                         if (cachedToString != null) 
666                                 return cachedToString;
667                         cachedToString = AbsoluteUri;
668
669                         return cachedToString;
670                 }
671
672                 void ISerializable.GetObjectData (SerializationInfo info, 
673                                           StreamingContext context)
674                 {
675                         info.AddValue ("AbsoluteUri", this.AbsoluteUri);
676                 }
677
678
679                 // Internal Methods             
680
681                 protected virtual void Escape ()
682                 {
683                         path = EscapeString (path);
684                 }
685
686                 protected static string EscapeString (string str) 
687                 {
688                         return EscapeString (str, false, true, true);
689                 }
690                 
691                 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets) 
692                 {
693                         if (str == null)
694                                 return String.Empty;
695                         
696                         StringBuilder s = new StringBuilder ();
697                         int len = str.Length;   
698                         for (int i = 0; i < len; i++) {
699                                 char c = str [i];
700                                 // reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
701                                 // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
702                                 // control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
703                                 // space       = <US-ASCII coded character 20 hexadecimal>
704                                 // delims      = "<" | ">" | "#" | "%" | <">
705                                 // unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
706
707                                 if ((c <= 0x20) || (c >= 0x7f) || 
708                                     ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
709                                     (escapeHex && (c == '#')) ||
710                                     (escapeBrackets && (c == '[' || c == ']')) ||
711                                     (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
712                                         // FIXME: EscapeString should allow wide characters (> 255). HexEscape rejects it.
713                                         s.Append (HexEscape (c));
714                                         continue;
715                                 }
716                                         
717                                 s.Append (c);
718                         }
719                         
720                         return s.ToString ();
721                 }
722
723                 [MonoTODO ("Find out what this should do!")]
724                 protected virtual void Parse ()
725                 {
726                 }       
727                 
728                 protected virtual string Unescape (string str) 
729                 {
730                         if (str == null)
731                                 return String.Empty;
732                         StringBuilder s = new StringBuilder ();
733                         int len = str.Length;
734                         for (int i = 0; i < len; i++) {
735                                 char c = str [i];
736                                 if (c == '%') {
737                                         s.Append (HexUnescape (str, ref i));
738                                         i--;
739                                 } else
740                                         s.Append (c);                                   
741                         }
742                         return s.ToString ();
743                 }
744
745                 
746                 // Private Methods
747                 
748                 // this parse method is as relaxed as possible about the format
749                 // it will hardly ever throw a UriFormatException
750                 private void Parse (string uriString)
751                 {                       
752                         //
753                         // From RFC 2396 :
754                         //
755                         //      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
756                         //       12            3  4          5       6  7        8 9
757                         //                      
758                         
759                         if (uriString == null)
760                                 throw new ArgumentNullException ("uriString");
761
762                         int len = uriString.Length;
763                         if (len <= 1) 
764                                 throw new UriFormatException ();
765
766                         // 1
767                         char c = 'x';
768                         int pos = 0;
769                         for (; pos < len; pos++) {
770                                 c = uriString [pos];
771                                 if ((c == ':') || (c == '/') || (c == '\\') || (c == '?') || (c == '#')) 
772                                         break;
773                         }
774
775                         if (pos == len)
776                                 throw new UriFormatException ("The format of the URI could not be determined.");
777
778                         if (c == '/') {
779                                 is_root_path = true;
780                         }
781
782                         if ((uriString.Length >= 2) &&
783                                         ((uriString [0] == '/') && (uriString [1] == '/')) ||
784                                         ((uriString [0] == '\\') || (uriString [1] == '\\'))) {
785                                 is_wins_dir = true;
786                                 is_root_path = true;
787                         }
788
789                         // 2 scheme
790                         if (c == ':') {
791                                 if (pos == 1) {
792                                         // a windows filepath
793                                         isWindowsFilePath = true;
794                                         scheme = Uri.UriSchemeFile;
795                                         path = uriString.Replace ('\\', '/');
796                                         return;
797                                 }
798
799                                 scheme = uriString.Substring (0, pos).ToLower ();
800                                 uriString = uriString.Remove (0, pos + 1);
801                         } else if ((c == '/') && (pos == 0)) {
802                                 scheme = Uri.UriSchemeFile;
803                                 if (uriString.Length > 1 && uriString [1] != '/')
804                                         // unix bare filepath
805                                         isUnixFilePath = true;
806                                 else
807                                         // unix UNC (kind of)
808                                         isUnc = true;
809                         } else {
810                                 scheme = Uri.UriSchemeFile;
811                                 if (uriString.StartsWith ("\\\\")) {
812                                         isUnc = true;
813                                         isWindowsFilePath = true;
814                                 }
815                                 if (uriString.Length > 8 && uriString [8] != '/')
816                                         isUnc = true;
817                         }
818
819                         // 3
820                         if ((uriString.Length >= 2) && 
821                             ((uriString [0] == '/') && (uriString [1] == '/')) ||
822                             ((uriString [0] == '\\') || (uriString [1] == '\\')))  {
823                                 if (scheme == Uri.UriSchemeFile)
824                                         uriString = uriString.TrimStart (new char [] {'/', '\\'});
825                                 else
826                                         uriString = uriString.Remove (0, 2);
827                         } else if (!IsPredefinedScheme (scheme)) {
828                                 path = uriString;
829                                 isOpaquePart = true;
830                                 return;
831                         }
832
833                         // 8 fragment
834                         pos = uriString.IndexOf ('#');
835                         if (!IsUnc && pos != -1) {
836                                 fragment = uriString.Substring (pos);
837                                 uriString = uriString.Substring (0, pos);
838                         }
839
840                         // 6 query
841                         pos = uriString.IndexOf ('?');
842                         if (pos != -1) {
843                                 query = uriString.Substring (pos);
844                                 uriString = uriString.Substring (0, pos);
845                         }
846                         
847                         // 5 path
848                         pos = uriString.IndexOfAny (new char[] {'/', '\\'});
849                         if (pos == -1) {
850                                 if ((scheme != Uri.UriSchemeMailto) &&
851                                     (scheme != Uri.UriSchemeNews) &&
852                                         (scheme != Uri.UriSchemeFile))
853                                         path = "/";
854                         } else {
855                                 path = uriString.Substring (pos).Replace ('\\', '/');
856                                 uriString = uriString.Substring (0, pos);
857                         }
858
859                         // 4.a user info
860                         pos = uriString.IndexOf ("@");
861                         if (pos != -1) {
862                                 userinfo = uriString.Substring (0, pos);
863                                 uriString = uriString.Remove (0, pos + 1);
864                         }
865
866                         // 4.b port
867                         port = -1;
868                         pos = uriString.LastIndexOf (":");
869                         if (pos != -1 && pos != (uriString.Length - 1)) {
870                                 string portStr = uriString.Remove (0, pos + 1);
871                                 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
872                                         try {
873                                                 port = (int) UInt32.Parse (portStr);
874                                                 uriString = uriString.Substring (0, pos);
875                                         } catch (Exception) {
876                                                 throw new UriFormatException ("Invalid URI: invalid port number");
877                                         }
878                                 }
879                         }
880                         if (port == -1) {
881                                 port = GetDefaultPort (scheme);
882                         }
883                         
884                         // 4 authority
885                         host = uriString;
886                         if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
887                                 try {
888                                         host = "[" + IPv6Address.Parse (host).ToString () + "]";
889                                 } catch (Exception) {
890                                         throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
891                                 }
892                         }
893
894                         if (host.Length == 2 && host [1] == ':') {
895                                 // windows filepath
896                                 path = host + path;
897                                 host = String.Empty;
898                         } else if (isUnixFilePath) {
899                                 uriString = "//" + uriString;
900                                 host = String.Empty;
901                         } else if (host.Length == 0) {
902                                 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
903                         } else if (scheme == UriSchemeFile) {
904                                 isUnc = true;
905                         }
906                 }
907
908                                 
909                 private struct UriScheme 
910                 {
911                         public string scheme;
912                         public string delimiter;
913                         public int defaultPort;
914
915                         public UriScheme (string s, string d, int p) 
916                         {
917                                 scheme = s;
918                                 delimiter = d;
919                                 defaultPort = p;
920                         }
921                 };
922
923                 static UriScheme [] schemes = new UriScheme [] {
924                         new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
925                         new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
926                         new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
927                         new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
928                         new UriScheme (UriSchemeMailto, ":", 25),
929                         new UriScheme (UriSchemeNews, ":", -1),
930                         new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
931                         new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
932                 };
933                                 
934                 internal static string GetSchemeDelimiter (string scheme) 
935                 {
936                         for (int i = 0; i < schemes.Length; i++) 
937                                 if (schemes [i].scheme == scheme)
938                                         return schemes [i].delimiter;
939                         return Uri.SchemeDelimiter;
940                 }
941                 
942                 internal static int GetDefaultPort (string scheme)
943                 {
944                         for (int i = 0; i < schemes.Length; i++) 
945                                 if (schemes [i].scheme == scheme)
946                                         return schemes [i].defaultPort;
947                         return -1;                      
948                 }
949
950                 private string GetOpaqueWiseSchemeDelimiter ()
951                 {
952                         if (isOpaquePart)
953                                 return ":";
954                         else
955                                 return GetSchemeDelimiter (scheme);
956                 }
957
958                 protected virtual bool IsBadFileSystemCharacter (char ch)
959                 {
960                         // It does not always overlap with InvalidPathChars.
961                         int chInt = (int) ch;
962                         if (chInt < 32 || (chInt < 64 && chInt > 57))
963                                 return true;
964                         switch (chInt) {
965                         case 0:
966                         case 34: // "
967                         case 38: // &
968                         case 42: // *
969                         case 44: // ,
970                         case 47: // /
971                         case 92: // \
972                         case 94: // ^
973                         case 124: // |
974                                 return true;
975                         }
976
977                         return false;
978                 }
979
980                 
981                 protected static bool IsExcludedCharacter (char ch)
982                 {
983                         if (ch <= 32 || ch >= 127)
984                                 return true;
985                         
986                         if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
987                             ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
988                             ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
989                             ch == '}')
990                                 return true;
991                         return false;
992                 }
993
994                 private static bool IsPredefinedScheme (string scheme)
995                 {
996                         switch (scheme) {
997                         case "http":
998                         case "https":
999                         case "file":
1000                         case "ftp":
1001                         case "nntp":
1002                         case "gopher":
1003                         case "mailto":
1004                         case "news":
1005                                 return true;
1006                         default:
1007                                 return false;
1008                         }
1009                 }
1010
1011                 protected virtual bool IsReservedCharacter (char ch)
1012                 {
1013                         if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1014                             ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1015                             ch == '@')
1016                                 return true;
1017                         return false;
1018                 }
1019         }
1020 }