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)
11 // Stephane Delcroix <stephane@delcroix.org>
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // (C) 2003 Novell inc.
17 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
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.
42 using System.Collections;
43 using System.Globalization;
45 // See RFC 2396 for more info on URI's.
47 // TODO: optimize by parsing host string only once
49 namespace Mono.Security {
70 // o scheme excludes the scheme delimiter
71 // o port is -1 to indicate no port is defined
72 // o path is empty or starts with / when scheme delimiter == "://"
73 // o query is empty or starts with ? char, escaped.
74 // o fragment is empty or starts with # char, unescaped.
75 // o all class variables are in escaped format when they are escapable,
76 // except cachedToString.
77 // o UNC is supported, as starts with "\\" for windows,
80 private bool isUnixFilePath = false;
81 private string source;
82 private string scheme = String.Empty;
83 private string host = String.Empty;
84 private int port = -1;
85 private string path = String.Empty;
86 private string query = String.Empty;
87 private string fragment = String.Empty;
88 private string userinfo = String.Empty;
89 private bool isUnc = false;
90 private bool isOpaquePart = false;
92 private string [] segments;
94 private bool userEscaped = false;
95 private string cachedAbsoluteUri = null;
96 private string cachedToString = null;
97 private string cachedLocalPath = null;
98 private int cachedHashCode = 0;
99 private bool reduce = true;
101 private static readonly string hexUpperChars = "0123456789ABCDEF";
105 public static readonly string SchemeDelimiter = "://";
106 public static readonly string UriSchemeFile = "file";
107 public static readonly string UriSchemeFtp = "ftp";
108 public static readonly string UriSchemeGopher = "gopher";
109 public static readonly string UriSchemeHttp = "http";
110 public static readonly string UriSchemeHttps = "https";
111 public static readonly string UriSchemeMailto = "mailto";
112 public static readonly string UriSchemeNews = "news";
113 public static readonly string UriSchemeNntp = "nntp";
117 public Uri (string uriString) : this (uriString, false)
121 public Uri (string uriString, bool dontEscape)
123 userEscaped = dontEscape;
128 public Uri (string uriString, bool dontEscape, bool reduce)
130 userEscaped = dontEscape;
132 this.reduce = reduce;
136 public Uri (Uri baseUri, string relativeUri)
137 : this (baseUri, relativeUri, false)
141 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
144 throw new NullReferenceException ("baseUri");
146 // See RFC 2396 Par 5.2 and Appendix C
148 userEscaped = dontEscape;
150 if (relativeUri == null)
151 throw new NullReferenceException ("relativeUri");
153 // Check Windows UNC (for // it is scheme/host separator)
154 if (relativeUri.StartsWith ("\\\\")) {
155 source = relativeUri;
160 int pos = relativeUri.IndexOf (':');
163 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
165 // pos2 < 0 ... e.g. mailto
166 // pos2 > pos ... to block ':' in query part
167 if (pos2 > pos || pos2 < 0) {
168 // equivalent to new Uri (relativeUri, dontEscape)
169 source = relativeUri;
176 this.scheme = baseUri.scheme;
177 this.host = baseUri.host;
178 this.port = baseUri.port;
179 this.userinfo = baseUri.userinfo;
180 this.isUnc = baseUri.isUnc;
181 this.isUnixFilePath = baseUri.isUnixFilePath;
182 this.isOpaquePart = baseUri.isOpaquePart;
184 if (relativeUri == String.Empty) {
185 this.path = baseUri.path;
186 this.query = baseUri.query;
187 this.fragment = baseUri.fragment;
192 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
193 pos = relativeUri.IndexOf ('#');
195 fragment = relativeUri.Substring (pos);
196 // fragment is not escaped.
197 relativeUri = relativeUri.Substring (0, pos);
201 pos = relativeUri.IndexOf ('?');
203 query = relativeUri.Substring (pos);
205 query = EscapeString (query);
206 relativeUri = relativeUri.Substring (0, pos);
209 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
210 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
211 source = scheme + ':' + relativeUri;
217 path = EscapeString (path);
224 if (relativeUri.Length > 0 || query.Length > 0) {
225 pos = path.LastIndexOf ('/');
227 path = path.Substring (0, pos + 1);
230 if(relativeUri.Length == 0)
239 pos = path.IndexOf ("./", startIndex);
243 path = path.Remove (0, 2);
244 else if (path [pos - 1] != '.')
245 path = path.Remove (pos, 2);
247 startIndex = pos + 1;
251 if (path.Length > 1 &&
252 path [path.Length - 1] == '.' &&
253 path [path.Length - 2] == '/')
254 path = path.Remove (path.Length - 1, 1);
259 pos = path.IndexOf ("/../", startIndex);
266 int pos2 = path.LastIndexOf ('/', pos - 1);
268 startIndex = pos + 1;
270 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
271 path = path.Remove (pos2 + 1, pos - pos2 + 3);
273 startIndex = pos + 1;
278 if (path.Length > 3 && path.EndsWith ("/..")) {
279 pos = path.LastIndexOf ('/', path.Length - 4);
281 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
282 path = path.Remove (pos + 1, path.Length - pos - 1);
286 path = EscapeString (path);
291 public string AbsolutePath {
295 public string AbsoluteUri {
297 if (cachedAbsoluteUri == null) {
298 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
300 return cachedAbsoluteUri;
304 public string Authority {
306 return (GetDefaultPort (scheme) == port)
307 ? host : host + ":" + port;
311 public string Fragment {
312 get { return fragment; }
319 /* public UriHostNameType HostNameType {
321 UriHostNameType ret = CheckHostName (host);
322 if (ret != UriHostNameType.Unknown)
325 // looks it always returns Basic...
326 return UriHostNameType.Basic; //.Unknown;
330 public bool IsDefaultPort {
331 get { return GetDefaultPort (scheme) == port; }
335 get { return (scheme == UriSchemeFile); }
338 public bool IsLoopback {
340 if (host == String.Empty)
343 if (host == "loopback" || host == "localhost")
347 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
349 } catch (FormatException) {}
352 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
353 } catch (FormatException) {}
360 // rule: This should be true only if
361 // - uri string starts from "\\", or
362 // - uri string starts from "//" (Samba way)
363 get { return isUnc; }
366 public string LocalPath {
368 if (cachedLocalPath != null)
369 return cachedLocalPath;
373 bool windows = (path.Length > 3 && path [1] == ':' &&
374 (path [2] == '\\' || path [2] == '/'));
377 string p = Unescape (path);
378 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
379 cachedLocalPath = p.Replace ('/', '\\');
383 // support *nix and W32 styles
384 if (path.Length > 1 && path [1] == ':')
385 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
387 // LAMESPEC: ok, now we cannot determine
388 // if such URI like "file://foo/bar" is
389 // Windows UNC or unix file path, so
390 // they should be handled differently.
391 else if (System.IO.Path.DirectorySeparatorChar == '\\')
392 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
394 cachedLocalPath = Unescape (path);
396 if (cachedLocalPath == String.Empty)
397 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
398 return cachedLocalPath;
402 public string PathAndQuery {
403 get { return path + query; }
410 public string Query {
411 get { return query; }
414 public string Scheme {
415 get { return scheme; }
418 public string [] Segments {
420 if (segments != null)
423 if (path.Length == 0) {
424 segments = new string [0];
428 string [] parts = path.Split ('/');
430 bool endSlash = path.EndsWith ("/");
431 if (parts.Length > 0 && endSlash) {
432 string [] newParts = new string [parts.Length - 1];
433 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
438 if (IsFile && path.Length > 1 && path [1] == ':') {
439 string [] newParts = new string [parts.Length + 1];
440 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
442 parts [0] = path.Substring (0, 2);
443 parts [1] = String.Empty;
447 int end = parts.Length;
449 if (i != end - 1 || endSlash)
457 public bool UserEscaped {
458 get { return userEscaped; }
461 public string UserInfo {
462 get { return userinfo; }
468 /* public static UriHostNameType CheckHostName (string name)
470 if (name == null || name.Length == 0)
471 return UriHostNameType.Unknown;
473 if (IsIPv4Address (name))
474 return UriHostNameType.IPv4;
476 if (IsDomainAddress (name))
477 return UriHostNameType.Dns;
480 IPv6Address.Parse (name);
481 return UriHostNameType.IPv6;
482 } catch (FormatException) {}
484 return UriHostNameType.Unknown;
487 internal static bool IsIPv4Address (string name)
489 string [] captures = name.Split (new char [] {'.'});
490 if (captures.Length != 4)
492 for (int i = 0; i < 4; i++) {
494 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
495 if (d < 0 || d > 255)
497 } catch (Exception) {
504 internal static bool IsDomainAddress (string name)
506 int len = name.Length;
508 if (name [len - 1] == '.')
512 for (int i = 0; i < len; i++) {
515 if (!Char.IsLetterOrDigit (c))
517 } else if (c == '.') {
519 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
529 public static bool CheckSchemeName (string schemeName)
531 if (schemeName == null || schemeName.Length == 0)
534 if (!Char.IsLetter (schemeName [0]))
537 int len = schemeName.Length;
538 for (int i = 1; i < len; i++) {
539 char c = schemeName [i];
540 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
547 public override bool Equals (object comparant)
549 if (comparant == null)
552 Uri uri = comparant as Uri;
554 string s = comparant as String;
560 CultureInfo inv = CultureInfo.InvariantCulture;
561 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
562 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
563 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
564 (this.port == uri.port) &&
565 (this.path == uri.path) &&
566 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
569 public override int GetHashCode ()
571 if (cachedHashCode == 0)
572 cachedHashCode = scheme.GetHashCode ()
573 + userinfo.GetHashCode ()
574 + host.GetHashCode ()
576 + path.GetHashCode ()
577 + query.GetHashCode ();
578 return cachedHashCode;
581 public string GetLeftPart (UriPartial part)
585 case UriPartial.Scheme :
586 return scheme + GetOpaqueWiseSchemeDelimiter ();
587 case UriPartial.Authority :
588 if (host == String.Empty ||
589 scheme == Uri.UriSchemeMailto ||
590 scheme == Uri.UriSchemeNews)
593 StringBuilder s = new StringBuilder ();
595 s.Append (GetOpaqueWiseSchemeDelimiter ());
596 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
597 s.Append ('/'); // win32 file
598 if (userinfo.Length > 0)
599 s.Append (userinfo).Append ('@');
601 defaultPort = GetDefaultPort (scheme);
602 if ((port != -1) && (port != defaultPort))
603 s.Append (':').Append (port);
604 return s.ToString ();
605 case UriPartial.Path :
606 StringBuilder sb = new StringBuilder ();
608 sb.Append (GetOpaqueWiseSchemeDelimiter ());
609 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
610 sb.Append ('/'); // win32 file
611 if (userinfo.Length > 0)
612 sb.Append (userinfo).Append ('@');
614 defaultPort = GetDefaultPort (scheme);
615 if ((port != -1) && (port != defaultPort))
616 sb.Append (':').Append (port);
618 return sb.ToString ();
623 public static int FromHex (char digit)
625 if ('0' <= digit && digit <= '9') {
626 return (int) (digit - '0');
629 if ('a' <= digit && digit <= 'f')
630 return (int) (digit - 'a' + 10);
632 if ('A' <= digit && digit <= 'F')
633 return (int) (digit - 'A' + 10);
635 throw new ArgumentException ("digit");
638 public static string HexEscape (char character)
640 if (character > 255) {
641 throw new ArgumentOutOfRangeException ("character");
644 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
645 + hexUpperChars [((character & 0x0f))];
648 public static char HexUnescape (string pattern, ref int index)
651 throw new ArgumentException ("pattern");
653 if (index < 0 || index >= pattern.Length)
654 throw new ArgumentOutOfRangeException ("index");
659 if (((index + 3) > pattern.Length) ||
660 (pattern [index] != '%') ||
661 !IsHexDigit (pattern [index + 1]) ||
662 !IsHexDigit (pattern [index + 2]))
665 return pattern [index++];
670 int msb = FromHex (pattern [index++]);
671 int lsb = FromHex (pattern [index++]);
672 int b = (msb << 4) + lsb;
680 } else if (b < 0xF0) {
683 } else if (b < 0xF8) {
686 } else if (b < 0xFB) {
689 } else if (b < 0xFE) {
693 c <<= (stage - 1) * 6;
696 c += (b - 0x80) << ((stage - 1) * 6);
697 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
704 public static bool IsHexDigit (char digit)
706 return (('0' <= digit && digit <= '9') ||
707 ('a' <= digit && digit <= 'f') ||
708 ('A' <= digit && digit <= 'F'));
711 public static bool IsHexEncoding (string pattern, int index)
713 if ((index + 3) > pattern.Length)
716 return ((pattern [index++] == '%') &&
717 IsHexDigit (pattern [index++]) &&
718 IsHexDigit (pattern [index]));
721 public string MakeRelative (Uri toUri)
723 if ((this.Scheme != toUri.Scheme) ||
724 (this.Authority != toUri.Authority))
725 return toUri.ToString ();
727 if (this.path == toUri.path)
730 string [] segments = this.Segments;
731 string [] segments2 = toUri.Segments;
734 int max = System.Math.Min (segments.Length, segments2.Length);
736 if (segments [k] != segments2 [k])
739 string result = String.Empty;
740 for (int i = k + 1; i < segments.Length; i++)
742 for (int i = k; i < segments2.Length; i++)
743 result += segments2 [i];
748 public override string ToString ()
750 if (cachedToString != null)
751 return cachedToString;
752 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
753 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
754 return cachedToString;
757 /* void ISerializable.GetObjectData (SerializationInfo info,
758 StreamingContext context)
760 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
766 protected void Escape ()
768 path = EscapeString (path);
771 protected static string EscapeString (string str)
773 return EscapeString (str, false, true, true);
776 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
781 StringBuilder s = new StringBuilder ();
782 int len = str.Length;
783 for (int i = 0; i < len; i++) {
784 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
785 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
786 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
787 // space = <US-ASCII coded character 20 hexadecimal>
788 // delims = "<" | ">" | "#" | "%" | <">
789 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
791 // check for escape code already placed in str,
792 // i.e. for encoding that follows the pattern
793 // "%hexhex" in a string, where "hex" is a digit from 0-9
794 // or a letter from A-F (case-insensitive).
795 if (IsHexEncoding (str,i)) {
796 // if ,yes , copy it as is
797 s.Append(str.Substring (i, 3));
802 byte [] data = Encoding.UTF8.GetBytes (new char[] {str[i]});
803 int length = data.Length;
804 for (int j = 0; j < length; j++) {
805 char c = (char) data [j];
806 if ((c <= 0x20) || (c >= 0x7f) ||
807 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
808 (escapeHex && (c == '#')) ||
809 (escapeBrackets && (c == '[' || c == ']')) ||
810 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
811 s.Append (HexEscape (c));
818 return s.ToString ();
821 // This method is called from .ctor(). When overriden, we can
822 // avoid the "absolute uri" constraints of the .ctor() by
823 // overriding with custom code.
824 protected void Parse ()
831 host = EscapeString (host, false, true, false);
832 path = EscapeString (path);
835 protected string Unescape (string str)
837 return Unescape (str, false);
840 internal string Unescape (string str, bool excludeSharp)
844 StringBuilder s = new StringBuilder ();
845 int len = str.Length;
846 for (int i = 0; i < len; i++) {
849 char x = HexUnescape (str, ref i);
850 if (excludeSharp && x == '#')
858 return s.ToString ();
864 private void ParseAsWindowsUNC (string uriString)
866 scheme = UriSchemeFile;
868 fragment = String.Empty;
869 query = String.Empty;
872 uriString = uriString.TrimStart (new char [] {'\\'});
873 int pos = uriString.IndexOf ('\\');
875 path = uriString.Substring (pos);
876 host = uriString.Substring (0, pos);
877 } else { // "\\\\server"
881 path = path.Replace ("\\", "/");
884 private void ParseAsWindowsAbsoluteFilePath (string uriString)
886 if (uriString.Length > 2 && uriString [2] != '\\'
887 && uriString [2] != '/')
888 throw new FormatException ("Relative file path is not allowed.");
889 scheme = UriSchemeFile;
892 path = uriString.Replace ("\\", "/");
893 fragment = String.Empty;
894 query = String.Empty;
897 private void ParseAsUnixAbsoluteFilePath (string uriString)
899 isUnixFilePath = true;
900 scheme = UriSchemeFile;
902 fragment = String.Empty;
903 query = String.Empty;
907 if (uriString.StartsWith ("//")) {
908 uriString = uriString.TrimStart (new char [] {'/'});
909 // Now we don't regard //foo/bar as "foo" host.
911 int pos = uriString.IndexOf ('/');
913 path = '/' + uriString.Substring (pos + 1);
914 host = uriString.Substring (0, pos);
915 } else { // "///server"
920 path = '/' + uriString;
926 // this parse method is as relaxed as possible about the format
927 // it will hardly ever throw a UriFormatException
928 private void Parse (string uriString)
933 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
937 if (uriString == null)
938 throw new ArgumentNullException ("uriString");
940 int len = uriString.Length;
942 throw new FormatException ();
947 // Identify Windows path, unix path, or standard URI.
948 pos = uriString.IndexOf (':');
950 // It must be Unix file path or Windows UNC
951 if (uriString [0] == '/')
952 ParseAsUnixAbsoluteFilePath (uriString);
953 else if (uriString.StartsWith ("\\\\"))
954 ParseAsWindowsUNC (uriString);
956 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
960 if (!Char.IsLetter (uriString [0]))
961 throw new FormatException ("URI scheme must start with alphabet character.");
962 // This means 'a:' == windows full path.
963 ParseAsWindowsAbsoluteFilePath (uriString);
968 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
969 // Check scheme name characters as specified in RFC2396.
970 if (!Char.IsLetter (scheme [0]))
971 throw new FormatException ("URI scheme must start with alphabet character.");
972 for (int i = 1; i < scheme.Length; i++) {
973 if (!Char.IsLetterOrDigit (scheme, i)) {
974 switch (scheme [i]) {
980 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
984 uriString = uriString.Substring (pos + 1);
987 pos = uriString.IndexOf ('#');
988 if (!IsUnc && pos != -1) {
989 fragment = uriString.Substring (pos);
990 uriString = uriString.Substring (0, pos);
994 pos = uriString.IndexOf ('?');
996 query = uriString.Substring (pos);
997 uriString = uriString.Substring (0, pos);
999 query = EscapeString (query);
1003 bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1004 if (uriString.StartsWith ("//")) {
1005 if (uriString.StartsWith ("////"))
1006 unixAbsPath = false;
1007 uriString = uriString.TrimStart (new char [] {'/'});
1008 if (uriString.Length > 1 && uriString [1] == ':')
1009 unixAbsPath = false;
1010 } else if (!IsPredefinedScheme (scheme)) {
1012 isOpaquePart = true;
1017 pos = uriString.IndexOfAny (new char[] {'/'});
1021 if ((scheme != Uri.UriSchemeMailto) &&
1022 (scheme != Uri.UriSchemeNews) &&
1023 (scheme != Uri.UriSchemeFile))
1026 path = uriString.Substring (pos);
1027 uriString = uriString.Substring (0, pos);
1031 pos = uriString.IndexOf ("@");
1033 userinfo = uriString.Substring (0, pos);
1034 uriString = uriString.Remove (0, pos + 1);
1039 pos = uriString.LastIndexOf (":");
1042 if (pos != -1 && pos != (uriString.Length - 1)) {
1043 string portStr = uriString.Remove (0, pos + 1);
1044 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1046 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1047 uriString = uriString.Substring (0, pos);
1048 } catch (Exception) {
1049 throw new FormatException ("Invalid URI: invalid port number");
1054 port = GetDefaultPort (scheme);
1059 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1061 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1062 } catch (Exception) {
1063 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1068 path = '/' + uriString;
1069 host = String.Empty;
1070 } else if (host.Length == 2 && host [1] == ':') {
1073 host = String.Empty;
1074 } else if (isUnixFilePath) {
1075 uriString = "//" + uriString;
1076 host = String.Empty;
1077 } else if (host.Length == 0) {
1078 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1079 } else if (scheme == UriSchemeFile) {
1083 if ((scheme != Uri.UriSchemeMailto) &&
1084 (scheme != Uri.UriSchemeNews) &&
1085 (scheme != Uri.UriSchemeFile))
1088 path = Reduce (path);
1091 private static string Reduce (string path)
1093 path = path.Replace ('\\','/');
1094 string [] parts = path.Split ('/');
1095 ArrayList result = new ArrayList ();
1097 int end = parts.Length;
1098 for (int i = 0; i < end; i++) {
1099 string current = parts [i];
1100 if (current.Length == 0 || current == "." )
1103 if (current == "..") {
1104 if (result.Count == 0) {
1105 if (i == 1) // see bug 52599
1107 throw new Exception ("Invalid path.");
1110 result.RemoveAt (result.Count - 1);
1114 result.Add (current);
1117 if (result.Count == 0)
1120 result.Insert (0, String.Empty);
1122 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1123 if (path.EndsWith ("/"))
1129 private struct UriScheme
1131 public string scheme;
1132 public string delimiter;
1133 public int defaultPort;
1135 public UriScheme (string s, string d, int p)
1143 static UriScheme [] schemes = new UriScheme [] {
1144 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1145 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1146 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1147 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1148 new UriScheme (UriSchemeMailto, ":", 25),
1149 new UriScheme (UriSchemeNews, ":", -1),
1150 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1151 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1154 internal static string GetSchemeDelimiter (string scheme)
1156 for (int i = 0; i < schemes.Length; i++)
1157 if (schemes [i].scheme == scheme)
1158 return schemes [i].delimiter;
1159 return Uri.SchemeDelimiter;
1162 internal static int GetDefaultPort (string scheme)
1164 for (int i = 0; i < schemes.Length; i++)
1165 if (schemes [i].scheme == scheme)
1166 return schemes [i].defaultPort;
1170 private string GetOpaqueWiseSchemeDelimiter ()
1175 return GetSchemeDelimiter (scheme);
1178 protected bool IsBadFileSystemCharacter (char ch)
1180 // It does not always overlap with InvalidPathChars.
1181 int chInt = (int) ch;
1182 if (chInt < 32 || (chInt < 64 && chInt > 57))
1201 protected static bool IsExcludedCharacter (char ch)
1203 if (ch <= 32 || ch >= 127)
1206 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1207 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1208 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1214 private static bool IsPredefinedScheme (string scheme)
1231 protected bool IsReservedCharacter (char ch)
1233 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1234 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||