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.Collections.Generic;
44 using System.Globalization;
46 // See RFC 2396 for more info on URI's.
48 // TODO: optimize by parsing host string only once
50 namespace Mono.Security {
71 // o scheme excludes the scheme delimiter
72 // o port is -1 to indicate no port is defined
73 // o path is empty or starts with / when scheme delimiter == "://"
74 // o query is empty or starts with ? char, escaped.
75 // o fragment is empty or starts with # char, unescaped.
76 // o all class variables are in escaped format when they are escapable,
77 // except cachedToString.
78 // o UNC is supported, as starts with "\\" for windows,
81 private bool isUnixFilePath = false;
82 private string source;
83 private string scheme = String.Empty;
84 private string host = String.Empty;
85 private int port = -1;
86 private string path = String.Empty;
87 private string query = String.Empty;
88 private string fragment = String.Empty;
89 private string userinfo = String.Empty;
90 private bool isUnc = false;
91 private bool isOpaquePart = false;
93 private string [] segments;
95 private bool userEscaped = false;
96 private string cachedAbsoluteUri = null;
97 private string cachedToString = null;
98 private string cachedLocalPath = null;
99 private int cachedHashCode = 0;
100 private bool reduce = true;
102 private static readonly string hexUpperChars = "0123456789ABCDEF";
106 public static readonly string SchemeDelimiter = "://";
107 public static readonly string UriSchemeFile = "file";
108 public static readonly string UriSchemeFtp = "ftp";
109 public static readonly string UriSchemeGopher = "gopher";
110 public static readonly string UriSchemeHttp = "http";
111 public static readonly string UriSchemeHttps = "https";
112 public static readonly string UriSchemeMailto = "mailto";
113 public static readonly string UriSchemeNews = "news";
114 public static readonly string UriSchemeNntp = "nntp";
118 public Uri (string uriString) : this (uriString, false)
122 public Uri (string uriString, bool dontEscape)
124 userEscaped = dontEscape;
129 public Uri (string uriString, bool dontEscape, bool reduce)
131 userEscaped = dontEscape;
133 this.reduce = reduce;
137 public Uri (Uri baseUri, string relativeUri)
138 : this (baseUri, relativeUri, false)
142 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
145 throw new NullReferenceException ("baseUri");
147 // See RFC 2396 Par 5.2 and Appendix C
149 userEscaped = dontEscape;
151 if (relativeUri == null)
152 throw new NullReferenceException ("relativeUri");
154 // Check Windows UNC (for // it is scheme/host separator)
155 if (relativeUri.StartsWith ("\\\\")) {
156 source = relativeUri;
161 int pos = relativeUri.IndexOf (':');
164 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
166 // pos2 < 0 ... e.g. mailto
167 // pos2 > pos ... to block ':' in query part
168 if (pos2 > pos || pos2 < 0) {
169 // equivalent to new Uri (relativeUri, dontEscape)
170 source = relativeUri;
177 this.scheme = baseUri.scheme;
178 this.host = baseUri.host;
179 this.port = baseUri.port;
180 this.userinfo = baseUri.userinfo;
181 this.isUnc = baseUri.isUnc;
182 this.isUnixFilePath = baseUri.isUnixFilePath;
183 this.isOpaquePart = baseUri.isOpaquePart;
185 if (relativeUri == String.Empty) {
186 this.path = baseUri.path;
187 this.query = baseUri.query;
188 this.fragment = baseUri.fragment;
193 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
194 pos = relativeUri.IndexOf ('#');
196 fragment = relativeUri.Substring (pos);
197 // fragment is not escaped.
198 relativeUri = relativeUri.Substring (0, pos);
202 pos = relativeUri.IndexOf ('?');
204 query = relativeUri.Substring (pos);
206 query = EscapeString (query);
207 relativeUri = relativeUri.Substring (0, pos);
210 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
211 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
212 source = scheme + ':' + relativeUri;
218 path = EscapeString (path);
225 if (relativeUri.Length > 0 || query.Length > 0) {
226 pos = path.LastIndexOf ('/');
228 path = path.Substring (0, pos + 1);
231 if(relativeUri.Length == 0)
240 pos = path.IndexOf ("./", startIndex);
244 path = path.Remove (0, 2);
245 else if (path [pos - 1] != '.')
246 path = path.Remove (pos, 2);
248 startIndex = pos + 1;
252 if (path.Length > 1 &&
253 path [path.Length - 1] == '.' &&
254 path [path.Length - 2] == '/')
255 path = path.Remove (path.Length - 1, 1);
260 pos = path.IndexOf ("/../", startIndex);
267 int pos2 = path.LastIndexOf ('/', pos - 1);
269 startIndex = pos + 1;
271 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
272 path = path.Remove (pos2 + 1, pos - pos2 + 3);
274 startIndex = pos + 1;
279 if (path.Length > 3 && path.EndsWith ("/..")) {
280 pos = path.LastIndexOf ('/', path.Length - 4);
282 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
283 path = path.Remove (pos + 1, path.Length - pos - 1);
287 path = EscapeString (path);
292 public string AbsolutePath {
296 public string AbsoluteUri {
298 if (cachedAbsoluteUri == null) {
299 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
301 return cachedAbsoluteUri;
305 public string Authority {
307 return (GetDefaultPort (scheme) == port)
308 ? host : host + ":" + port;
312 public string Fragment {
313 get { return fragment; }
320 /* public UriHostNameType HostNameType {
322 UriHostNameType ret = CheckHostName (host);
323 if (ret != UriHostNameType.Unknown)
326 // looks it always returns Basic...
327 return UriHostNameType.Basic; //.Unknown;
331 public bool IsDefaultPort {
332 get { return GetDefaultPort (scheme) == port; }
336 get { return (scheme == UriSchemeFile); }
339 public bool IsLoopback {
341 if (host == String.Empty)
344 if (host == "loopback" || host == "localhost")
348 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
350 } catch (FormatException) {}
353 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
354 } catch (FormatException) {}
361 // rule: This should be true only if
362 // - uri string starts from "\\", or
363 // - uri string starts from "//" (Samba way)
364 get { return isUnc; }
367 public string LocalPath {
369 if (cachedLocalPath != null)
370 return cachedLocalPath;
374 bool windows = (path.Length > 3 && path [1] == ':' &&
375 (path [2] == '\\' || path [2] == '/'));
378 string p = Unescape (path);
379 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
380 cachedLocalPath = p.Replace ('/', '\\');
384 // support *nix and W32 styles
385 if (path.Length > 1 && path [1] == ':')
386 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
388 // LAMESPEC: ok, now we cannot determine
389 // if such URI like "file://foo/bar" is
390 // Windows UNC or unix file path, so
391 // they should be handled differently.
392 else if (System.IO.Path.DirectorySeparatorChar == '\\')
393 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
395 cachedLocalPath = Unescape (path);
397 if (cachedLocalPath == String.Empty)
398 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
399 return cachedLocalPath;
403 public string PathAndQuery {
404 get { return path + query; }
411 public string Query {
412 get { return query; }
415 public string Scheme {
416 get { return scheme; }
419 public string [] Segments {
421 if (segments != null)
424 if (path.Length == 0) {
425 segments = new string [0];
429 string [] parts = path.Split ('/');
431 bool endSlash = path.EndsWith ("/");
432 if (parts.Length > 0 && endSlash) {
433 string [] newParts = new string [parts.Length - 1];
434 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
439 if (IsFile && path.Length > 1 && path [1] == ':') {
440 string [] newParts = new string [parts.Length + 1];
441 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
443 parts [0] = path.Substring (0, 2);
444 parts [1] = String.Empty;
448 int end = parts.Length;
450 if (i != end - 1 || endSlash)
458 public bool UserEscaped {
459 get { return userEscaped; }
462 public string UserInfo {
463 get { return userinfo; }
469 /* public static UriHostNameType CheckHostName (string name)
471 if (name == null || name.Length == 0)
472 return UriHostNameType.Unknown;
474 if (IsIPv4Address (name))
475 return UriHostNameType.IPv4;
477 if (IsDomainAddress (name))
478 return UriHostNameType.Dns;
481 IPv6Address.Parse (name);
482 return UriHostNameType.IPv6;
483 } catch (FormatException) {}
485 return UriHostNameType.Unknown;
488 internal static bool IsIPv4Address (string name)
490 string [] captures = name.Split (new char [] {'.'});
491 if (captures.Length != 4)
493 for (int i = 0; i < 4; i++) {
495 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
496 if (d < 0 || d > 255)
498 } catch (Exception) {
505 internal static bool IsDomainAddress (string name)
507 int len = name.Length;
509 if (name [len - 1] == '.')
513 for (int i = 0; i < len; i++) {
516 if (!Char.IsLetterOrDigit (c))
518 } else if (c == '.') {
520 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
530 public static bool CheckSchemeName (string schemeName)
532 if (schemeName == null || schemeName.Length == 0)
535 if (!Char.IsLetter (schemeName [0]))
538 int len = schemeName.Length;
539 for (int i = 1; i < len; i++) {
540 char c = schemeName [i];
541 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
548 public override bool Equals (object comparant)
550 if (comparant == null)
553 Uri uri = comparant as Uri;
555 string s = comparant as String;
561 CultureInfo inv = CultureInfo.InvariantCulture;
562 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
563 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
564 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
565 (this.port == uri.port) &&
566 (this.path == uri.path) &&
567 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
570 public override int GetHashCode ()
572 if (cachedHashCode == 0)
573 cachedHashCode = scheme.GetHashCode ()
574 + userinfo.GetHashCode ()
575 + host.GetHashCode ()
577 + path.GetHashCode ()
578 + query.GetHashCode ();
579 return cachedHashCode;
582 public string GetLeftPart (UriPartial part)
586 case UriPartial.Scheme :
587 return scheme + GetOpaqueWiseSchemeDelimiter ();
588 case UriPartial.Authority :
589 if (host == String.Empty ||
590 scheme == Uri.UriSchemeMailto ||
591 scheme == Uri.UriSchemeNews)
594 StringBuilder s = new StringBuilder ();
596 s.Append (GetOpaqueWiseSchemeDelimiter ());
597 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
598 s.Append ('/'); // win32 file
599 if (userinfo.Length > 0)
600 s.Append (userinfo).Append ('@');
602 defaultPort = GetDefaultPort (scheme);
603 if ((port != -1) && (port != defaultPort))
604 s.Append (':').Append (port);
605 return s.ToString ();
606 case UriPartial.Path :
607 StringBuilder sb = new StringBuilder ();
609 sb.Append (GetOpaqueWiseSchemeDelimiter ());
610 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
611 sb.Append ('/'); // win32 file
612 if (userinfo.Length > 0)
613 sb.Append (userinfo).Append ('@');
615 defaultPort = GetDefaultPort (scheme);
616 if ((port != -1) && (port != defaultPort))
617 sb.Append (':').Append (port);
619 return sb.ToString ();
624 public static int FromHex (char digit)
626 if ('0' <= digit && digit <= '9') {
627 return (int) (digit - '0');
630 if ('a' <= digit && digit <= 'f')
631 return (int) (digit - 'a' + 10);
633 if ('A' <= digit && digit <= 'F')
634 return (int) (digit - 'A' + 10);
636 throw new ArgumentException ("digit");
639 public static string HexEscape (char character)
641 if (character > 255) {
642 throw new ArgumentOutOfRangeException ("character");
645 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
646 + hexUpperChars [((character & 0x0f))];
649 public static char HexUnescape (string pattern, ref int index)
652 throw new ArgumentException ("pattern");
654 if (index < 0 || index >= pattern.Length)
655 throw new ArgumentOutOfRangeException ("index");
660 if (((index + 3) > pattern.Length) ||
661 (pattern [index] != '%') ||
662 !IsHexDigit (pattern [index + 1]) ||
663 !IsHexDigit (pattern [index + 2]))
666 return pattern [index++];
671 int msb = FromHex (pattern [index++]);
672 int lsb = FromHex (pattern [index++]);
673 int b = (msb << 4) + lsb;
681 } else if (b < 0xF0) {
684 } else if (b < 0xF8) {
687 } else if (b < 0xFB) {
690 } else if (b < 0xFE) {
694 c <<= (stage - 1) * 6;
697 c += (b - 0x80) << ((stage - 1) * 6);
698 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
705 public static bool IsHexDigit (char digit)
707 return (('0' <= digit && digit <= '9') ||
708 ('a' <= digit && digit <= 'f') ||
709 ('A' <= digit && digit <= 'F'));
712 public static bool IsHexEncoding (string pattern, int index)
714 if ((index + 3) > pattern.Length)
717 return ((pattern [index++] == '%') &&
718 IsHexDigit (pattern [index++]) &&
719 IsHexDigit (pattern [index]));
722 public string MakeRelative (Uri toUri)
724 if ((this.Scheme != toUri.Scheme) ||
725 (this.Authority != toUri.Authority))
726 return toUri.ToString ();
728 if (this.path == toUri.path)
731 string [] segments = this.Segments;
732 string [] segments2 = toUri.Segments;
735 int max = System.Math.Min (segments.Length, segments2.Length);
737 if (segments [k] != segments2 [k])
740 string result = String.Empty;
741 for (int i = k + 1; i < segments.Length; i++)
743 for (int i = k; i < segments2.Length; i++)
744 result += segments2 [i];
749 public override string ToString ()
751 if (cachedToString != null)
752 return cachedToString;
753 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
754 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
755 return cachedToString;
758 /* void ISerializable.GetObjectData (SerializationInfo info,
759 StreamingContext context)
761 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
767 protected void Escape ()
769 path = EscapeString (path);
772 protected static string EscapeString (string str)
774 return EscapeString (str, false, true, true);
777 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
782 StringBuilder s = new StringBuilder ();
783 int len = str.Length;
784 for (int i = 0; i < len; i++) {
785 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
786 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
787 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
788 // space = <US-ASCII coded character 20 hexadecimal>
789 // delims = "<" | ">" | "#" | "%" | <">
790 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
792 // check for escape code already placed in str,
793 // i.e. for encoding that follows the pattern
794 // "%hexhex" in a string, where "hex" is a digit from 0-9
795 // or a letter from A-F (case-insensitive).
796 if (IsHexEncoding (str,i)) {
797 // if ,yes , copy it as is
798 s.Append(str.Substring (i, 3));
803 byte [] data = Encoding.UTF8.GetBytes (new char[] {str[i]});
804 int length = data.Length;
805 for (int j = 0; j < length; j++) {
806 char c = (char) data [j];
807 if ((c <= 0x20) || (c >= 0x7f) ||
808 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
809 (escapeHex && (c == '#')) ||
810 (escapeBrackets && (c == '[' || c == ']')) ||
811 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
812 s.Append (HexEscape (c));
819 return s.ToString ();
822 // This method is called from .ctor(). When overriden, we can
823 // avoid the "absolute uri" constraints of the .ctor() by
824 // overriding with custom code.
825 protected void Parse ()
832 host = EscapeString (host, false, true, false);
833 path = EscapeString (path);
836 protected string Unescape (string str)
838 return Unescape (str, false);
841 internal string Unescape (string str, bool excludeSharp)
845 StringBuilder s = new StringBuilder ();
846 int len = str.Length;
847 for (int i = 0; i < len; i++) {
850 char x = HexUnescape (str, ref i);
851 if (excludeSharp && x == '#')
859 return s.ToString ();
865 private void ParseAsWindowsUNC (string uriString)
867 scheme = UriSchemeFile;
869 fragment = String.Empty;
870 query = String.Empty;
873 uriString = uriString.TrimStart (new char [] {'\\'});
874 int pos = uriString.IndexOf ('\\');
876 path = uriString.Substring (pos);
877 host = uriString.Substring (0, pos);
878 } else { // "\\\\server"
882 path = path.Replace ("\\", "/");
885 private void ParseAsWindowsAbsoluteFilePath (string uriString)
887 if (uriString.Length > 2 && uriString [2] != '\\'
888 && uriString [2] != '/')
889 throw new FormatException ("Relative file path is not allowed.");
890 scheme = UriSchemeFile;
893 path = uriString.Replace ("\\", "/");
894 fragment = String.Empty;
895 query = String.Empty;
898 private void ParseAsUnixAbsoluteFilePath (string uriString)
900 isUnixFilePath = true;
901 scheme = UriSchemeFile;
903 fragment = String.Empty;
904 query = String.Empty;
908 if (uriString.StartsWith ("//")) {
909 uriString = uriString.TrimStart (new char [] {'/'});
910 // Now we don't regard //foo/bar as "foo" host.
912 int pos = uriString.IndexOf ('/');
914 path = '/' + uriString.Substring (pos + 1);
915 host = uriString.Substring (0, pos);
916 } else { // "///server"
921 path = '/' + uriString;
927 // this parse method is as relaxed as possible about the format
928 // it will hardly ever throw a UriFormatException
929 private void Parse (string uriString)
934 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
938 if (uriString == null)
939 throw new ArgumentNullException ("uriString");
941 int len = uriString.Length;
943 throw new FormatException ();
948 // Identify Windows path, unix path, or standard URI.
949 pos = uriString.IndexOf (':');
951 // It must be Unix file path or Windows UNC
952 if (uriString [0] == '/')
953 ParseAsUnixAbsoluteFilePath (uriString);
954 else if (uriString.StartsWith ("\\\\"))
955 ParseAsWindowsUNC (uriString);
957 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
961 if (!Char.IsLetter (uriString [0]))
962 throw new FormatException ("URI scheme must start with alphabet character.");
963 // This means 'a:' == windows full path.
964 ParseAsWindowsAbsoluteFilePath (uriString);
969 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
970 // Check scheme name characters as specified in RFC2396.
971 if (!Char.IsLetter (scheme [0]))
972 throw new FormatException ("URI scheme must start with alphabet character.");
973 for (int i = 1; i < scheme.Length; i++) {
974 if (!Char.IsLetterOrDigit (scheme, i)) {
975 switch (scheme [i]) {
981 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
985 uriString = uriString.Substring (pos + 1);
988 pos = uriString.IndexOf ('#');
989 if (!IsUnc && pos != -1) {
990 fragment = uriString.Substring (pos);
991 uriString = uriString.Substring (0, pos);
995 pos = uriString.IndexOf ('?');
997 query = uriString.Substring (pos);
998 uriString = uriString.Substring (0, pos);
1000 query = EscapeString (query);
1004 bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1005 if (uriString.StartsWith ("//")) {
1006 if (uriString.StartsWith ("////"))
1007 unixAbsPath = false;
1008 uriString = uriString.TrimStart (new char [] {'/'});
1009 if (uriString.Length > 1 && uriString [1] == ':')
1010 unixAbsPath = false;
1011 } else if (!IsPredefinedScheme (scheme)) {
1013 isOpaquePart = true;
1018 pos = uriString.IndexOfAny (new char[] {'/'});
1022 if ((scheme != Uri.UriSchemeMailto) &&
1023 (scheme != Uri.UriSchemeNews) &&
1024 (scheme != Uri.UriSchemeFile))
1027 path = uriString.Substring (pos);
1028 uriString = uriString.Substring (0, pos);
1032 pos = uriString.IndexOf ("@");
1036 userinfo = uriString.Substring (0, pos);
1037 uriString = uriString.Remove (0, pos + 1);
1042 pos = uriString.LastIndexOf (":");
1045 if (pos != -1 && pos != (uriString.Length - 1)) {
1046 string portStr = uriString.Remove (0, pos + 1);
1047 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1049 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1050 uriString = uriString.Substring (0, pos);
1051 } catch (Exception) {
1052 throw new FormatException ("Invalid URI: invalid port number");
1057 port = GetDefaultPort (scheme);
1062 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1064 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1065 } catch (Exception) {
1066 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1071 path = '/' + uriString;
1072 host = String.Empty;
1073 } else if (host.Length == 2 && host [1] == ':') {
1076 host = String.Empty;
1077 } else if (isUnixFilePath) {
1078 uriString = "//" + uriString;
1079 host = String.Empty;
1080 } else if (host.Length == 0) {
1081 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1082 } else if (scheme == UriSchemeFile) {
1086 if ((scheme != Uri.UriSchemeMailto) &&
1087 (scheme != Uri.UriSchemeNews) &&
1088 (scheme != Uri.UriSchemeFile))
1091 path = Reduce (path);
1094 private static string Reduce (string path)
1096 path = path.Replace ('\\','/');
1097 string [] parts = path.Split ('/');
1098 var result = new List<string> ();
1100 int end = parts.Length;
1101 for (int i = 0; i < end; i++) {
1102 string current = parts [i];
1103 if (current.Length == 0 || current == "." )
1106 if (current == "..") {
1107 if (result.Count == 0) {
1108 if (i == 1) // see bug 52599
1110 throw new Exception ("Invalid path.");
1113 result.RemoveAt (result.Count - 1);
1117 result.Add (current);
1120 if (result.Count == 0)
1123 result.Insert (0, String.Empty);
1125 string res = String.Join ("/", result.ToArray ());
1126 if (path.EndsWith ("/"))
1132 private struct UriScheme
1134 public string scheme;
1135 public string delimiter;
1136 public int defaultPort;
1138 public UriScheme (string s, string d, int p)
1146 static UriScheme [] schemes = new UriScheme [] {
1147 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1148 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1149 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1150 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1151 new UriScheme (UriSchemeMailto, ":", 25),
1152 new UriScheme (UriSchemeNews, ":", -1),
1153 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1154 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1157 internal static string GetSchemeDelimiter (string scheme)
1159 for (int i = 0; i < schemes.Length; i++)
1160 if (schemes [i].scheme == scheme)
1161 return schemes [i].delimiter;
1162 return Uri.SchemeDelimiter;
1165 internal static int GetDefaultPort (string scheme)
1167 for (int i = 0; i < schemes.Length; i++)
1168 if (schemes [i].scheme == scheme)
1169 return schemes [i].defaultPort;
1173 private string GetOpaqueWiseSchemeDelimiter ()
1178 return GetSchemeDelimiter (scheme);
1181 protected bool IsBadFileSystemCharacter (char ch)
1183 // It does not always overlap with InvalidPathChars.
1184 int chInt = (int) ch;
1185 if (chInt < 32 || (chInt < 64 && chInt > 57))
1204 protected static bool IsExcludedCharacter (char ch)
1206 if (ch <= 32 || ch >= 127)
1209 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1210 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1211 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1217 private static bool IsPredefinedScheme (string scheme)
1234 protected bool IsReservedCharacter (char ch)
1236 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1237 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||