3 // Adapted from System.Uri (in System.dll assembly) for its use in corlib
6 // Lawrence Pit (loz@cable.a2000.nl)
7 // Garrett Rooney (rooneg@electricjellyfish.net)
8 // Ian MacLean (ianm@activestate.com)
9 // Ben Maurer (bmaurer@users.sourceforge.net)
10 // Atsushi Enomoto (atsushi@ximian.com)
12 // (C) 2001 Garrett Rooney
13 // (C) 2003 Ian MacLean
14 // (C) 2003 Ben Maurer
15 // (C) 2003 Novell inc.
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Collections;
42 using System.Globalization;
44 // See RFC 2396 for more info on URI's.
46 // TODO: optimize by parsing host string only once
48 namespace Mono.Security {
69 // o scheme excludes the scheme delimiter
70 // o port is -1 to indicate no port is defined
71 // o path is empty or starts with / when scheme delimiter == "://"
72 // o query is empty or starts with ? char, escaped.
73 // o fragment is empty or starts with # char, unescaped.
74 // o all class variables are in escaped format when they are escapable,
75 // except cachedToString.
76 // o UNC is supported, as starts with "\\" for windows,
79 private bool isUnixFilePath = false;
80 private string source;
81 private string scheme = String.Empty;
82 private string host = String.Empty;
83 private int port = -1;
84 private string path = String.Empty;
85 private string query = String.Empty;
86 private string fragment = String.Empty;
87 private string userinfo = String.Empty;
88 private bool isUnc = false;
89 private bool isOpaquePart = false;
91 private string [] segments;
93 private bool userEscaped = false;
94 private string cachedAbsoluteUri = null;
95 private string cachedToString = null;
96 private string cachedLocalPath = null;
97 private int cachedHashCode = 0;
98 private bool reduce = true;
100 private static readonly string hexUpperChars = "0123456789ABCDEF";
104 public static readonly string SchemeDelimiter = "://";
105 public static readonly string UriSchemeFile = "file";
106 public static readonly string UriSchemeFtp = "ftp";
107 public static readonly string UriSchemeGopher = "gopher";
108 public static readonly string UriSchemeHttp = "http";
109 public static readonly string UriSchemeHttps = "https";
110 public static readonly string UriSchemeMailto = "mailto";
111 public static readonly string UriSchemeNews = "news";
112 public static readonly string UriSchemeNntp = "nntp";
116 public Uri (string uriString) : this (uriString, false)
120 public Uri (string uriString, bool dontEscape)
122 userEscaped = dontEscape;
127 public Uri (string uriString, bool dontEscape, bool reduce)
129 userEscaped = dontEscape;
131 this.reduce = reduce;
135 public Uri (Uri baseUri, string relativeUri)
136 : this (baseUri, relativeUri, false)
140 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
143 throw new NullReferenceException ("baseUri");
145 // See RFC 2396 Par 5.2 and Appendix C
147 userEscaped = dontEscape;
149 if (relativeUri == null)
150 throw new NullReferenceException ("relativeUri");
152 // Check Windows UNC (for // it is scheme/host separator)
153 if (relativeUri.StartsWith ("\\\\")) {
154 source = relativeUri;
159 int pos = relativeUri.IndexOf (':');
162 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
164 // pos2 < 0 ... e.g. mailto
165 // pos2 > pos ... to block ':' in query part
166 if (pos2 > pos || pos2 < 0) {
167 // equivalent to new Uri (relativeUri, dontEscape)
168 source = relativeUri;
175 this.scheme = baseUri.scheme;
176 this.host = baseUri.host;
177 this.port = baseUri.port;
178 this.userinfo = baseUri.userinfo;
179 this.isUnc = baseUri.isUnc;
180 this.isUnixFilePath = baseUri.isUnixFilePath;
181 this.isOpaquePart = baseUri.isOpaquePart;
183 if (relativeUri == String.Empty) {
184 this.path = baseUri.path;
185 this.query = baseUri.query;
186 this.fragment = baseUri.fragment;
191 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
192 pos = relativeUri.IndexOf ('#');
194 fragment = relativeUri.Substring (pos);
195 // fragment is not escaped.
196 relativeUri = relativeUri.Substring (0, pos);
200 pos = relativeUri.IndexOf ('?');
202 query = relativeUri.Substring (pos);
204 query = EscapeString (query);
205 relativeUri = relativeUri.Substring (0, pos);
208 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
209 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
210 source = scheme + ':' + relativeUri;
216 path = EscapeString (path);
223 if (relativeUri.Length > 0 || query.Length > 0) {
224 pos = path.LastIndexOf ('/');
226 path = path.Substring (0, pos + 1);
229 if(relativeUri.Length == 0)
238 pos = path.IndexOf ("./", startIndex);
242 path = path.Remove (0, 2);
243 else if (path [pos - 1] != '.')
244 path = path.Remove (pos, 2);
246 startIndex = pos + 1;
250 if (path.Length > 1 &&
251 path [path.Length - 1] == '.' &&
252 path [path.Length - 2] == '/')
253 path = path.Remove (path.Length - 1, 1);
258 pos = path.IndexOf ("/../", startIndex);
265 int pos2 = path.LastIndexOf ('/', pos - 1);
267 startIndex = pos + 1;
269 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
270 path = path.Remove (pos2 + 1, pos - pos2 + 3);
272 startIndex = pos + 1;
277 if (path.Length > 3 && path.EndsWith ("/..")) {
278 pos = path.LastIndexOf ('/', path.Length - 4);
280 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
281 path = path.Remove (pos + 1, path.Length - pos - 1);
285 path = EscapeString (path);
290 public string AbsolutePath {
294 public string AbsoluteUri {
296 if (cachedAbsoluteUri == null) {
297 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
299 return cachedAbsoluteUri;
303 public string Authority {
305 return (GetDefaultPort (scheme) == port)
306 ? host : host + ":" + port;
310 public string Fragment {
311 get { return fragment; }
318 /* public UriHostNameType HostNameType {
320 UriHostNameType ret = CheckHostName (host);
321 if (ret != UriHostNameType.Unknown)
324 // looks it always returns Basic...
325 return UriHostNameType.Basic; //.Unknown;
329 public bool IsDefaultPort {
330 get { return GetDefaultPort (scheme) == port; }
334 get { return (scheme == UriSchemeFile); }
337 public bool IsLoopback {
339 if (host == String.Empty)
342 if (host == "loopback" || host == "localhost")
346 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
348 } catch (FormatException) {}
351 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
352 } catch (FormatException) {}
359 // rule: This should be true only if
360 // - uri string starts from "\\", or
361 // - uri string starts from "//" (Samba way)
362 get { return isUnc; }
365 public string LocalPath {
367 if (cachedLocalPath != null)
368 return cachedLocalPath;
372 bool windows = (path.Length > 3 && path [1] == ':' &&
373 (path [2] == '\\' || path [2] == '/'));
376 string p = Unescape (path);
377 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
378 cachedLocalPath = p.Replace ('/', '\\');
382 // support *nix and W32 styles
383 if (path.Length > 1 && path [1] == ':')
384 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
386 // LAMESPEC: ok, now we cannot determine
387 // if such URI like "file://foo/bar" is
388 // Windows UNC or unix file path, so
389 // they should be handled differently.
390 else if (System.IO.Path.DirectorySeparatorChar == '\\')
391 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
393 cachedLocalPath = Unescape (path);
395 if (cachedLocalPath == String.Empty)
396 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
397 return cachedLocalPath;
401 public string PathAndQuery {
402 get { return path + query; }
409 public string Query {
410 get { return query; }
413 public string Scheme {
414 get { return scheme; }
417 public string [] Segments {
419 if (segments != null)
423 segments = new string [0];
427 string [] parts = path.Split ('/');
429 bool endSlash = path.EndsWith ("/");
430 if (parts.Length > 0 && endSlash) {
431 string [] newParts = new string [parts.Length - 1];
432 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
437 if (IsFile && path.Length > 1 && path [1] == ':') {
438 string [] newParts = new string [parts.Length + 1];
439 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
441 parts [0] = path.Substring (0, 2);
446 int end = parts.Length;
448 if (i != end - 1 || endSlash)
456 public bool UserEscaped {
457 get { return userEscaped; }
460 public string UserInfo {
461 get { return userinfo; }
467 /* public static UriHostNameType CheckHostName (string name)
469 if (name == null || name.Length == 0)
470 return UriHostNameType.Unknown;
472 if (IsIPv4Address (name))
473 return UriHostNameType.IPv4;
475 if (IsDomainAddress (name))
476 return UriHostNameType.Dns;
479 IPv6Address.Parse (name);
480 return UriHostNameType.IPv6;
481 } catch (FormatException) {}
483 return UriHostNameType.Unknown;
486 internal static bool IsIPv4Address (string name)
488 string [] captures = name.Split (new char [] {'.'});
489 if (captures.Length != 4)
491 for (int i = 0; i < 4; i++) {
493 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
494 if (d < 0 || d > 255)
496 } catch (Exception) {
503 internal static bool IsDomainAddress (string name)
505 int len = name.Length;
507 if (name [len - 1] == '.')
511 for (int i = 0; i < len; i++) {
514 if (!Char.IsLetterOrDigit (c))
516 } else if (c == '.') {
518 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
528 /* [MonoTODO ("Find out what this should do")]
529 protected virtual void Canonicalize ()
533 public static bool CheckSchemeName (string schemeName)
535 if (schemeName == null || schemeName.Length == 0)
538 if (!Char.IsLetter (schemeName [0]))
541 int len = schemeName.Length;
542 for (int i = 1; i < len; i++) {
543 char c = schemeName [i];
544 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
551 /* [MonoTODO ("Find out what this should do")]
552 protected virtual void CheckSecurity ()
556 public override bool Equals (object comparant)
558 if (comparant == null)
561 Uri uri = comparant as Uri;
563 string s = comparant as String;
569 CultureInfo inv = CultureInfo.InvariantCulture;
570 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
571 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
572 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
573 (this.port == uri.port) &&
574 (this.path == uri.path) &&
575 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
578 public override int GetHashCode ()
580 if (cachedHashCode == 0)
581 cachedHashCode = scheme.GetHashCode ()
582 + userinfo.GetHashCode ()
583 + host.GetHashCode ()
585 + path.GetHashCode ()
586 + query.GetHashCode ();
587 return cachedHashCode;
590 public string GetLeftPart (UriPartial part)
594 case UriPartial.Scheme :
595 return scheme + GetOpaqueWiseSchemeDelimiter ();
596 case UriPartial.Authority :
597 if (host == String.Empty ||
598 scheme == Uri.UriSchemeMailto ||
599 scheme == Uri.UriSchemeNews)
602 StringBuilder s = new StringBuilder ();
604 s.Append (GetOpaqueWiseSchemeDelimiter ());
605 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
606 s.Append ('/'); // win32 file
607 if (userinfo.Length > 0)
608 s.Append (userinfo).Append ('@');
610 defaultPort = GetDefaultPort (scheme);
611 if ((port != -1) && (port != defaultPort))
612 s.Append (':').Append (port);
613 return s.ToString ();
614 case UriPartial.Path :
615 StringBuilder sb = new StringBuilder ();
617 sb.Append (GetOpaqueWiseSchemeDelimiter ());
618 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
619 sb.Append ('/'); // win32 file
620 if (userinfo.Length > 0)
621 sb.Append (userinfo).Append ('@');
623 defaultPort = GetDefaultPort (scheme);
624 if ((port != -1) && (port != defaultPort))
625 sb.Append (':').Append (port);
627 return sb.ToString ();
632 public static int FromHex (char digit)
634 if ('0' <= digit && digit <= '9') {
635 return (int) (digit - '0');
638 if ('a' <= digit && digit <= 'f')
639 return (int) (digit - 'a' + 10);
641 if ('A' <= digit && digit <= 'F')
642 return (int) (digit - 'A' + 10);
644 throw new ArgumentException ("digit");
647 public static string HexEscape (char character)
649 if (character > 255) {
650 throw new ArgumentOutOfRangeException ("character");
653 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
654 + hexUpperChars [((character & 0x0f))];
657 public static char HexUnescape (string pattern, ref int index)
660 throw new ArgumentException ("pattern");
662 if (index < 0 || index >= pattern.Length)
663 throw new ArgumentOutOfRangeException ("index");
668 if (((index + 3) > pattern.Length) ||
669 (pattern [index] != '%') ||
670 !IsHexDigit (pattern [index + 1]) ||
671 !IsHexDigit (pattern [index + 2]))
674 return pattern [index++];
679 int msb = FromHex (pattern [index++]);
680 int lsb = FromHex (pattern [index++]);
681 int b = (msb << 4) + lsb;
689 } else if (b < 0xF0) {
692 } else if (b < 0xF8) {
695 } else if (b < 0xFB) {
698 } else if (b < 0xFE) {
702 c <<= (stage - 1) * 6;
705 c += (b - 0x80) << ((stage - 1) * 6);
706 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
713 public static bool IsHexDigit (char digit)
715 return (('0' <= digit && digit <= '9') ||
716 ('a' <= digit && digit <= 'f') ||
717 ('A' <= digit && digit <= 'F'));
720 public static bool IsHexEncoding (string pattern, int index)
722 if ((index + 3) > pattern.Length)
725 return ((pattern [index++] == '%') &&
726 IsHexDigit (pattern [index++]) &&
727 IsHexDigit (pattern [index]));
730 public string MakeRelative (Uri toUri)
732 if ((this.Scheme != toUri.Scheme) ||
733 (this.Authority != toUri.Authority))
734 return toUri.ToString ();
736 if (this.path == toUri.path)
739 string [] segments = this.Segments;
740 string [] segments2 = toUri.Segments;
743 int max = System.Math.Min (segments.Length, segments2.Length);
745 if (segments [k] != segments2 [k])
748 string result = String.Empty;
749 for (int i = k + 1; i < segments.Length; i++)
751 for (int i = k; i < segments2.Length; i++)
752 result += segments2 [i];
757 public override string ToString ()
759 if (cachedToString != null)
760 return cachedToString;
761 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
762 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
763 return cachedToString;
766 /* void ISerializable.GetObjectData (SerializationInfo info,
767 StreamingContext context)
769 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
775 protected void Escape ()
777 path = EscapeString (path);
780 protected static string EscapeString (string str)
782 return EscapeString (str, false, true, true);
785 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
790 byte [] data = Encoding.UTF8.GetBytes (str.ToCharArray ());
791 StringBuilder s = new StringBuilder ();
792 int len = data.Length;
793 for (int i = 0; i < len; i++) {
794 char c = (char) data [i];
795 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
796 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
797 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
798 // space = <US-ASCII coded character 20 hexadecimal>
799 // delims = "<" | ">" | "#" | "%" | <">
800 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
802 // check for escape code already placed in str,
803 // i.e. for encoding that follows the pattern
804 // "%hexhex" in a string, where "hex" is a digit from 0-9
805 // or a letter from A-F (case-insensitive).
806 if('%'.Equals(c) && IsHexEncoding(str,i))
808 // if ,yes , copy it as is
815 if ((c <= 0x20) || (c >= 0x7f) ||
816 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
817 (escapeHex && (c == '#')) ||
818 (escapeBrackets && (c == '[' || c == ']')) ||
819 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
820 s.Append (HexEscape (c));
828 return s.ToString ();
831 // This method is called from .ctor(). When overriden, we can
832 // avoid the "absolute uri" constraints of the .ctor() by
833 // overriding with custom code.
834 protected void Parse ()
841 host = EscapeString (host, false, true, false);
842 path = EscapeString (path);
845 protected string Unescape (string str)
847 return Unescape (str, false);
850 internal string Unescape (string str, bool excludeSharp)
854 StringBuilder s = new StringBuilder ();
855 int len = str.Length;
856 for (int i = 0; i < len; i++) {
859 char x = HexUnescape (str, ref i);
860 if (excludeSharp && x == '#')
868 return s.ToString ();
874 private void ParseAsWindowsUNC (string uriString)
876 scheme = UriSchemeFile;
878 fragment = String.Empty;
879 query = String.Empty;
882 uriString = uriString.TrimStart (new char [] {'\\'});
883 int pos = uriString.IndexOf ('\\');
885 path = uriString.Substring (pos);
886 host = uriString.Substring (0, pos);
887 } else { // "\\\\server"
891 path = path.Replace ("\\", "/");
894 private void ParseAsWindowsAbsoluteFilePath (string uriString)
896 if (uriString.Length > 2 && uriString [2] != '\\'
897 && uriString [2] != '/')
898 throw new FormatException ("Relative file path is not allowed.");
899 scheme = UriSchemeFile;
902 path = uriString.Replace ("\\", "/");
903 fragment = String.Empty;
904 query = String.Empty;
907 private void ParseAsUnixAbsoluteFilePath (string uriString)
909 isUnixFilePath = true;
910 scheme = UriSchemeFile;
912 fragment = String.Empty;
913 query = String.Empty;
917 if (uriString.StartsWith ("//")) {
918 uriString = uriString.TrimStart (new char [] {'/'});
919 // Now we don't regard //foo/bar as "foo" host.
921 int pos = uriString.IndexOf ('/');
923 path = '/' + uriString.Substring (pos + 1);
924 host = uriString.Substring (0, pos);
925 } else { // "///server"
930 path = '/' + uriString;
936 // this parse method is as relaxed as possible about the format
937 // it will hardly ever throw a UriFormatException
938 private void Parse (string uriString)
943 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
947 if (uriString == null)
948 throw new ArgumentNullException ("uriString");
950 int len = uriString.Length;
952 throw new FormatException ();
957 // Identify Windows path, unix path, or standard URI.
958 pos = uriString.IndexOf (':');
960 // It must be Unix file path or Windows UNC
961 if (uriString [0] == '/')
962 ParseAsUnixAbsoluteFilePath (uriString);
963 else if (uriString.StartsWith ("\\\\"))
964 ParseAsWindowsUNC (uriString);
966 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
970 if (!Char.IsLetter (uriString [0]))
971 throw new FormatException ("URI scheme must start with alphabet character.");
972 // This means 'a:' == windows full path.
973 ParseAsWindowsAbsoluteFilePath (uriString);
978 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
979 // Check scheme name characters as specified in RFC2396.
980 if (!Char.IsLetter (scheme [0]))
981 throw new FormatException ("URI scheme must start with alphabet character.");
982 for (int i = 1; i < scheme.Length; i++) {
983 if (!Char.IsLetterOrDigit (scheme, i)) {
984 switch (scheme [i]) {
990 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
994 uriString = uriString.Substring (pos + 1);
997 pos = uriString.IndexOf ('#');
998 if (!IsUnc && pos != -1) {
999 fragment = uriString.Substring (pos);
1000 uriString = uriString.Substring (0, pos);
1004 pos = uriString.IndexOf ('?');
1006 query = uriString.Substring (pos);
1007 uriString = uriString.Substring (0, pos);
1009 query = EscapeString (query);
1013 bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1014 if (uriString.StartsWith ("//")) {
1015 if (uriString.StartsWith ("////"))
1016 unixAbsPath = false;
1017 uriString = uriString.TrimStart (new char [] {'/'});
1018 if (uriString.Length > 1 && uriString [1] == ':')
1019 unixAbsPath = false;
1020 } else if (!IsPredefinedScheme (scheme)) {
1022 isOpaquePart = true;
1027 pos = uriString.IndexOfAny (new char[] {'/'});
1031 if ((scheme != Uri.UriSchemeMailto) &&
1032 (scheme != Uri.UriSchemeNews) &&
1033 (scheme != Uri.UriSchemeFile))
1036 path = uriString.Substring (pos);
1037 uriString = uriString.Substring (0, pos);
1041 pos = uriString.IndexOf ("@");
1043 userinfo = uriString.Substring (0, pos);
1044 uriString = uriString.Remove (0, pos + 1);
1049 pos = uriString.LastIndexOf (":");
1052 if (pos != -1 && pos != (uriString.Length - 1)) {
1053 string portStr = uriString.Remove (0, pos + 1);
1054 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1056 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1057 uriString = uriString.Substring (0, pos);
1058 } catch (Exception) {
1059 throw new FormatException ("Invalid URI: invalid port number");
1064 port = GetDefaultPort (scheme);
1069 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1071 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1072 } catch (Exception) {
1073 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1078 path = '/' + uriString;
1079 host = String.Empty;
1080 } else if (host.Length == 2 && host [1] == ':') {
1083 host = String.Empty;
1084 } else if (isUnixFilePath) {
1085 uriString = "//" + uriString;
1086 host = String.Empty;
1087 } else if (host.Length == 0) {
1088 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1089 } else if (scheme == UriSchemeFile) {
1093 if ((scheme != Uri.UriSchemeMailto) &&
1094 (scheme != Uri.UriSchemeNews) &&
1095 (scheme != Uri.UriSchemeFile))
1098 path = Reduce (path);
1101 private static string Reduce (string path)
1103 path = path.Replace ('\\','/');
1104 string [] parts = path.Split ('/');
1105 ArrayList result = new ArrayList ();
1107 int end = parts.Length;
1108 for (int i = 0; i < end; i++) {
1109 string current = parts [i];
1110 if (current == "" || current == "." )
1113 if (current == "..") {
1114 if (result.Count == 0) {
1115 if (i == 1) // see bug 52599
1117 throw new Exception ("Invalid path.");
1120 result.RemoveAt (result.Count - 1);
1124 result.Add (current);
1127 if (result.Count == 0)
1130 result.Insert (0, "");
1132 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1133 if (path.EndsWith ("/"))
1139 private struct UriScheme
1141 public string scheme;
1142 public string delimiter;
1143 public int defaultPort;
1145 public UriScheme (string s, string d, int p)
1153 static UriScheme [] schemes = new UriScheme [] {
1154 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1155 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1156 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1157 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1158 new UriScheme (UriSchemeMailto, ":", 25),
1159 new UriScheme (UriSchemeNews, ":", -1),
1160 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1161 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1164 internal static string GetSchemeDelimiter (string scheme)
1166 for (int i = 0; i < schemes.Length; i++)
1167 if (schemes [i].scheme == scheme)
1168 return schemes [i].delimiter;
1169 return Uri.SchemeDelimiter;
1172 internal static int GetDefaultPort (string scheme)
1174 for (int i = 0; i < schemes.Length; i++)
1175 if (schemes [i].scheme == scheme)
1176 return schemes [i].defaultPort;
1180 private string GetOpaqueWiseSchemeDelimiter ()
1185 return GetSchemeDelimiter (scheme);
1188 protected bool IsBadFileSystemCharacter (char ch)
1190 // It does not always overlap with InvalidPathChars.
1191 int chInt = (int) ch;
1192 if (chInt < 32 || (chInt < 64 && chInt > 57))
1211 protected static bool IsExcludedCharacter (char ch)
1213 if (ch <= 32 || ch >= 127)
1216 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1217 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1218 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1224 private static bool IsPredefinedScheme (string scheme)
1241 protected bool IsReservedCharacter (char ch)
1243 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1244 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||