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)
11 // (C) 2001 Garrett Rooney
12 // (C) 2003 Ian MacLean
13 // (C) 2003 Ben Maurer
14 // (C) 2003 Novell inc.
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.
39 using System.Runtime.Serialization;
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
51 public class Uri : MarshalByRefObject, ISerializable
54 // o scheme excludes the scheme delimiter
55 // o port is -1 to indicate no port is defined
56 // o path is empty or starts with / when scheme delimiter == "://"
57 // o query is empty or starts with ? char, escaped.
58 // o fragment is empty or starts with # char, unescaped.
59 // o all class variables are in escaped format when they are escapable,
60 // except cachedToString.
61 // o UNC is supported, as starts with "\\" for windows,
64 private bool isUnixFilePath = false;
65 private string source;
66 private string scheme = String.Empty;
67 private string host = String.Empty;
68 private int port = -1;
69 private string path = String.Empty;
70 private string query = String.Empty;
71 private string fragment = String.Empty;
72 private string userinfo = String.Empty;
73 private bool isUnc = false;
74 private bool isOpaquePart = false;
76 private string [] segments;
78 private bool userEscaped = false;
79 private string cachedAbsoluteUri = null;
80 private string cachedToString = null;
81 private string cachedLocalPath = null;
82 private int cachedHashCode = 0;
84 private static readonly string hexUpperChars = "0123456789ABCDEF";
88 public static readonly string SchemeDelimiter = "://";
89 public static readonly string UriSchemeFile = "file";
90 public static readonly string UriSchemeFtp = "ftp";
91 public static readonly string UriSchemeGopher = "gopher";
92 public static readonly string UriSchemeHttp = "http";
93 public static readonly string UriSchemeHttps = "https";
94 public static readonly string UriSchemeMailto = "mailto";
95 public static readonly string UriSchemeNews = "news";
96 public static readonly string UriSchemeNntp = "nntp";
100 public Uri (string uriString) : this (uriString, false)
104 protected Uri (SerializationInfo serializationInfo,
105 StreamingContext streamingContext) :
106 this (serializationInfo.GetString ("AbsoluteUri"), true)
110 public Uri (string uriString, bool dontEscape)
112 userEscaped = dontEscape;
117 public Uri (Uri baseUri, string relativeUri)
118 : this (baseUri, relativeUri, false)
122 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
125 throw new NullReferenceException ("baseUri");
127 // See RFC 2396 Par 5.2 and Appendix C
129 userEscaped = dontEscape;
131 if (relativeUri == null)
132 throw new NullReferenceException ("relativeUri");
134 // Check Windows UNC (for // it is scheme/host separator)
135 if (relativeUri.StartsWith ("\\\\")) {
136 source = relativeUri;
141 int pos = relativeUri.IndexOf (':');
144 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
146 // pos2 < 0 ... e.g. mailto
147 // pos2 > pos ... to block ':' in query part
148 if (pos2 > pos || pos2 < 0) {
149 // equivalent to new Uri (relativeUri, dontEscape)
150 source = relativeUri;
157 this.scheme = baseUri.scheme;
158 this.host = baseUri.host;
159 this.port = baseUri.port;
160 this.userinfo = baseUri.userinfo;
161 this.isUnc = baseUri.isUnc;
162 this.isUnixFilePath = baseUri.isUnixFilePath;
163 this.isOpaquePart = baseUri.isOpaquePart;
165 if (relativeUri == String.Empty) {
166 this.path = baseUri.path;
167 this.query = baseUri.query;
168 this.fragment = baseUri.fragment;
173 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
174 pos = relativeUri.IndexOf ('#');
177 fragment = relativeUri.Substring (pos);
179 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
180 relativeUri = relativeUri.Substring (0, pos);
184 pos = relativeUri.IndexOf ('?');
186 query = relativeUri.Substring (pos);
188 query = EscapeString (query);
189 relativeUri = relativeUri.Substring (0, pos);
192 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
193 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
194 source = scheme + ':' + relativeUri;
200 path = EscapeString (path);
207 if (relativeUri.Length > 0 || query.Length > 0) {
208 pos = path.LastIndexOf ('/');
210 path = path.Substring (0, pos + 1);
213 if(relativeUri.Length == 0)
222 pos = path.IndexOf ("./", startIndex);
226 path = path.Remove (0, 2);
227 else if (path [pos - 1] != '.')
228 path = path.Remove (pos, 2);
230 startIndex = pos + 1;
234 if (path.Length > 1 &&
235 path [path.Length - 1] == '.' &&
236 path [path.Length - 2] == '/')
237 path = path.Remove (path.Length - 1, 1);
242 pos = path.IndexOf ("/../", startIndex);
249 int pos2 = path.LastIndexOf ('/', pos - 1);
251 startIndex = pos + 1;
253 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
254 path = path.Remove (pos2 + 1, pos - pos2 + 3);
256 startIndex = pos + 1;
261 if (path.Length > 3 && path.EndsWith ("/..")) {
262 pos = path.LastIndexOf ('/', path.Length - 4);
264 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
265 path = path.Remove (pos + 1, path.Length - pos - 1);
269 path = EscapeString (path);
274 public string AbsolutePath {
278 public string AbsoluteUri {
280 if (cachedAbsoluteUri == null) {
281 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
283 return cachedAbsoluteUri;
287 public string Authority {
289 return (GetDefaultPort (scheme) == port)
290 ? host : host + ":" + port;
294 public string Fragment {
295 get { return fragment; }
302 public UriHostNameType HostNameType {
304 UriHostNameType ret = CheckHostName (host);
305 if (ret != UriHostNameType.Unknown)
308 // looks it always returns Basic...
309 return UriHostNameType.Basic; //.Unknown;
313 public bool IsDefaultPort {
314 get { return GetDefaultPort (scheme) == port; }
318 get { return (scheme == UriSchemeFile); }
321 public bool IsLoopback {
323 if (host == String.Empty)
326 if (host == "loopback" || host == "localhost")
330 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
332 } catch (FormatException) {}
335 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
336 } catch (FormatException) {}
343 // rule: This should be true only if
344 // - uri string starts from "\\", or
345 // - uri string starts from "//" (Samba way)
346 get { return isUnc; }
349 public string LocalPath {
351 if (cachedLocalPath != null)
352 return cachedLocalPath;
356 bool windows = (path.Length > 3 && path [1] == ':' &&
357 (path [2] == '\\' || path [2] == '/'));
360 string p = Unescape (path);
361 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
362 cachedLocalPath = p.Replace ('/', '\\');
366 // support *nix and W32 styles
367 if (path.Length > 1 && path [1] == ':')
368 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
370 // LAMESPEC: ok, now we cannot determine
371 // if such URI like "file://foo/bar" is
372 // Windows UNC or unix file path, so
373 // they should be handled differently.
374 else if (System.IO.Path.DirectorySeparatorChar == '\\')
375 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
377 cachedLocalPath = Unescape (path);
379 if (cachedLocalPath == String.Empty)
380 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
381 return cachedLocalPath;
385 public string PathAndQuery {
386 get { return path + query; }
393 public string Query {
394 get { return query; }
397 public string Scheme {
398 get { return scheme; }
401 public string [] Segments {
403 if (segments != null)
407 segments = new string [0];
411 string [] parts = path.Split ('/');
413 bool endSlash = path.EndsWith ("/");
414 if (parts.Length > 0 && endSlash) {
415 string [] newParts = new string [parts.Length - 1];
416 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
421 if (IsFile && path.Length > 1 && path [1] == ':') {
422 string [] newParts = new string [parts.Length + 1];
423 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
425 parts [0] = path.Substring (0, 2);
430 int end = parts.Length;
432 if (i != end - 1 || endSlash)
440 public bool UserEscaped {
441 get { return userEscaped; }
444 public string UserInfo {
445 get { return userinfo; }
451 public static UriHostNameType CheckHostName (string name)
453 if (name == null || name.Length == 0)
454 return UriHostNameType.Unknown;
456 if (IsIPv4Address (name))
457 return UriHostNameType.IPv4;
459 if (IsDomainAddress (name))
460 return UriHostNameType.Dns;
463 IPv6Address.Parse (name);
464 return UriHostNameType.IPv6;
465 } catch (FormatException) {}
467 return UriHostNameType.Unknown;
470 internal static bool IsIPv4Address (string name)
472 string [] captures = name.Split (new char [] {'.'});
473 if (captures.Length != 4)
475 for (int i = 0; i < 4; i++) {
477 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
478 if (d < 0 || d > 255)
480 } catch (Exception) {
487 internal static bool IsDomainAddress (string name)
489 int len = name.Length;
491 if (name [len - 1] == '.')
495 for (int i = 0; i < len; i++) {
498 if (!Char.IsLetterOrDigit (c))
500 } else if (c == '.') {
502 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
512 [MonoTODO ("Find out what this should do")]
513 protected virtual void Canonicalize ()
517 public static bool CheckSchemeName (string schemeName)
519 if (schemeName == null || schemeName.Length == 0)
522 if (!Char.IsLetter (schemeName [0]))
525 int len = schemeName.Length;
526 for (int i = 1; i < len; i++) {
527 char c = schemeName [i];
528 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
535 [MonoTODO ("Find out what this should do")]
536 protected virtual void CheckSecurity ()
540 public override bool Equals (object comparant)
542 if (comparant == null)
545 Uri uri = comparant as Uri;
547 string s = comparant as String;
553 CultureInfo inv = CultureInfo.InvariantCulture;
554 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
555 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
556 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
557 (this.port == uri.port) &&
558 (this.path == uri.path) &&
559 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
562 public override int GetHashCode ()
564 if (cachedHashCode == 0)
565 cachedHashCode = scheme.GetHashCode ()
566 + userinfo.GetHashCode ()
567 + host.GetHashCode ()
569 + path.GetHashCode ()
570 + query.GetHashCode ();
571 return cachedHashCode;
574 public string GetLeftPart (UriPartial part)
578 case UriPartial.Scheme :
579 return scheme + GetOpaqueWiseSchemeDelimiter ();
580 case UriPartial.Authority :
581 if (host == String.Empty ||
582 scheme == Uri.UriSchemeMailto ||
583 scheme == Uri.UriSchemeNews)
586 StringBuilder s = new StringBuilder ();
588 s.Append (GetOpaqueWiseSchemeDelimiter ());
589 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
590 s.Append ('/'); // win32 file
591 if (userinfo.Length > 0)
592 s.Append (userinfo).Append ('@');
594 defaultPort = GetDefaultPort (scheme);
595 if ((port != -1) && (port != defaultPort))
596 s.Append (':').Append (port);
597 return s.ToString ();
598 case UriPartial.Path :
599 StringBuilder sb = new StringBuilder ();
601 sb.Append (GetOpaqueWiseSchemeDelimiter ());
602 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
603 sb.Append ('/'); // win32 file
604 if (userinfo.Length > 0)
605 sb.Append (userinfo).Append ('@');
607 defaultPort = GetDefaultPort (scheme);
608 if ((port != -1) && (port != defaultPort))
609 sb.Append (':').Append (port);
611 return sb.ToString ();
616 public static int FromHex (char digit)
618 if ('0' <= digit && digit <= '9') {
619 return (int) (digit - '0');
622 if ('a' <= digit && digit <= 'f')
623 return (int) (digit - 'a' + 10);
625 if ('A' <= digit && digit <= 'F')
626 return (int) (digit - 'A' + 10);
628 throw new ArgumentException ("digit");
631 public static string HexEscape (char character)
633 if (character > 255) {
634 throw new ArgumentOutOfRangeException ("character");
637 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
638 + hexUpperChars [((character & 0x0f))];
641 public static char HexUnescape (string pattern, ref int index)
644 throw new ArgumentException ("pattern");
646 if (index < 0 || index >= pattern.Length)
647 throw new ArgumentOutOfRangeException ("index");
649 if (!IsHexEncoding (pattern, index))
650 return pattern [index++];
658 int msb = FromHex (pattern [index++]);
659 int lsb = FromHex (pattern [index++]);
660 b = (msb << 4) + lsb;
661 if (!IsHexEncoding (pattern, index)) {
663 c += (b - 0x80) << ((stage - 1) * 6);
667 } else if (stage == 0) {
673 } else if (b < 0xF0) {
676 } else if (b < 0xF8) {
679 } else if (b < 0xFB) {
682 } else if (b < 0xFE) {
686 c <<= (stage - 1) * 6;
688 c += (b - 0x80) << ((stage - 1) * 6);
697 public static bool IsHexDigit (char digit)
699 return (('0' <= digit && digit <= '9') ||
700 ('a' <= digit && digit <= 'f') ||
701 ('A' <= digit && digit <= 'F'));
704 public static bool IsHexEncoding (string pattern, int index)
706 if ((index + 3) > pattern.Length)
709 return ((pattern [index++] == '%') &&
710 IsHexDigit (pattern [index++]) &&
711 IsHexDigit (pattern [index]));
714 public string MakeRelative (Uri toUri)
716 if ((this.Scheme != toUri.Scheme) ||
717 (this.Authority != toUri.Authority))
718 return toUri.ToString ();
720 if (this.path == toUri.path)
723 string [] segments = this.Segments;
724 string [] segments2 = toUri.Segments;
727 int max = Math.Min (segments.Length, segments2.Length);
729 if (segments [k] != segments2 [k])
732 string result = String.Empty;
733 for (int i = k + 1; i < segments.Length; i++)
735 for (int i = k; i < segments2.Length; i++)
736 result += segments2 [i];
741 public override string ToString ()
743 if (cachedToString != null)
744 return cachedToString;
745 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
746 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
747 return cachedToString;
750 void ISerializable.GetObjectData (SerializationInfo info,
751 StreamingContext context)
753 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
759 protected virtual void Escape ()
761 path = EscapeString (path);
764 protected static string EscapeString (string str)
766 return EscapeString (str, false, true, true);
769 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
774 byte [] data = Encoding.UTF8.GetBytes (str.ToCharArray ());
775 StringBuilder s = new StringBuilder ();
776 int len = data.Length;
777 for (int i = 0; i < len; i++) {
778 char c = (char) data [i];
779 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
780 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
781 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
782 // space = <US-ASCII coded character 20 hexadecimal>
783 // delims = "<" | ">" | "#" | "%" | <">
784 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
786 // check for escape code already placed in str,
787 // i.e. for encoding that follows the pattern
788 // "%hexhex" in a string, where "hex" is a digit from 0-9
789 // or a letter from A-F (case-insensitive).
790 if('%' == c && IsHexEncoding(str,i))
792 // if ,yes , copy it as is
799 if ((c <= 0x20) || (c >= 0x7f) ||
800 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
801 (escapeHex && (c == '#')) ||
802 (escapeBrackets && (c == '[' || c == ']')) ||
803 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
804 s.Append (HexEscape (c));
812 return s.ToString ();
815 // This method is called from .ctor(). When overriden, we can
816 // avoid the "absolute uri" constraints of the .ctor() by
817 // overriding with custom code.
818 protected virtual void Parse ()
825 host = EscapeString (host, false, true, false);
826 path = EscapeString (path);
829 protected virtual string Unescape (string str)
831 return Unescape (str, false);
834 private string Unescape (string str, bool excludeSharp)
838 StringBuilder s = new StringBuilder ();
839 int len = str.Length;
840 for (int i = 0; i < len; i++) {
843 char x = HexUnescape (str, ref i);
844 if (excludeSharp && x == '#')
852 return s.ToString ();
858 private void ParseAsWindowsUNC (string uriString)
860 scheme = UriSchemeFile;
862 fragment = String.Empty;
863 query = String.Empty;
866 uriString = uriString.TrimStart (new char [] {'\\'});
867 int pos = uriString.IndexOf ('\\');
869 path = uriString.Substring (pos);
870 host = uriString.Substring (0, pos);
871 } else { // "\\\\server"
875 path = path.Replace ("\\", "/");
878 private void ParseAsWindowsAbsoluteFilePath (string uriString)
880 if (uriString.Length > 2 && uriString [2] != '\\'
881 && uriString [2] != '/')
882 throw new UriFormatException ("Relative file path is not allowed.");
883 scheme = UriSchemeFile;
886 path = uriString.Replace ("\\", "/");
887 fragment = String.Empty;
888 query = String.Empty;
891 private void ParseAsUnixAbsoluteFilePath (string uriString)
893 isUnixFilePath = true;
894 scheme = UriSchemeFile;
896 fragment = String.Empty;
897 query = String.Empty;
901 if (uriString.StartsWith ("//")) {
902 uriString = uriString.TrimStart (new char [] {'/'});
903 // Now we don't regard //foo/bar as "foo" host.
905 int pos = uriString.IndexOf ('/');
907 path = '/' + uriString.Substring (pos + 1);
908 host = uriString.Substring (0, pos);
909 } else { // "///server"
914 path = '/' + uriString;
920 // this parse method is as relaxed as possible about the format
921 // it will hardly ever throw a UriFormatException
922 private void Parse (string uriString)
927 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
931 if (uriString == null)
932 throw new ArgumentNullException ("uriString");
934 int len = uriString.Length;
936 throw new UriFormatException ();
941 // Identify Windows path, unix path, or standard URI.
942 pos = uriString.IndexOf (':');
944 // It must be Unix file path or Windows UNC
945 if (uriString [0] == '/')
946 ParseAsUnixAbsoluteFilePath (uriString);
947 else if (uriString.StartsWith ("\\\\"))
948 ParseAsWindowsUNC (uriString);
950 throw new UriFormatException ("URI scheme was not recognized, and input string was not recognized as an absolute file path.");
954 if (!Char.IsLetter (uriString [0]))
955 throw new UriFormatException ("URI scheme must start with a letter.");
956 // This means 'a:' == windows full path.
957 ParseAsWindowsAbsoluteFilePath (uriString);
962 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
963 // Check scheme name characters as specified in RFC2396.
964 if (!Char.IsLetter (scheme [0]))
965 throw new UriFormatException ("URI scheme must start with a letter.");
966 for (int i = 1; i < scheme.Length; i++) {
967 if (!Char.IsLetterOrDigit (scheme, i)) {
968 switch (scheme [i]) {
974 throw new UriFormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
978 uriString = uriString.Substring (pos + 1);
981 pos = uriString.IndexOf ('#');
982 if (!IsUnc && pos != -1) {
984 fragment = uriString.Substring (pos);
986 fragment = "#" + EscapeString (uriString.Substring (pos+1));
988 uriString = uriString.Substring (0, pos);
992 pos = uriString.IndexOf ('?');
994 query = uriString.Substring (pos);
995 uriString = uriString.Substring (0, pos);
997 query = EscapeString (query);
1001 bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1002 if (uriString.StartsWith ("//")) {
1003 if (uriString.StartsWith ("////"))
1004 unixAbsPath = false;
1005 uriString = uriString.TrimStart (new char [] {'/'});
1006 if (uriString.Length > 1 && uriString [1] == ':')
1007 unixAbsPath = false;
1008 } else if (!IsPredefinedScheme (scheme)) {
1010 isOpaquePart = true;
1015 pos = uriString.IndexOfAny (new char[] {'/'});
1019 if ((scheme != Uri.UriSchemeMailto) &&
1020 (scheme != Uri.UriSchemeNews) &&
1021 (scheme != Uri.UriSchemeFile))
1024 path = uriString.Substring (pos);
1025 uriString = uriString.Substring (0, pos);
1029 pos = uriString.IndexOf ("@");
1031 userinfo = uriString.Substring (0, pos);
1032 uriString = uriString.Remove (0, pos + 1);
1037 pos = uriString.LastIndexOf (":");
1040 if (pos != -1 && pos != (uriString.Length - 1)) {
1041 string portStr = uriString.Remove (0, pos + 1);
1042 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1044 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1045 uriString = uriString.Substring (0, pos);
1046 } catch (Exception) {
1047 throw new UriFormatException ("Invalid URI: Invalid port number");
1052 port = GetDefaultPort (scheme);
1057 if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1059 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1060 } catch (Exception) {
1061 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1066 path = '/' + uriString;
1067 host = String.Empty;
1068 } else if (host.Length == 2 && host [1] == ':') {
1071 host = String.Empty;
1072 } else if (isUnixFilePath) {
1073 uriString = "//" + uriString;
1074 host = String.Empty;
1075 } else if (host.Length == 0) {
1076 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1077 } else if (scheme == UriSchemeFile) {
1081 if ((scheme != Uri.UriSchemeMailto) &&
1082 (scheme != Uri.UriSchemeNews) &&
1083 (scheme != Uri.UriSchemeFile))
1084 path = Reduce (path);
1087 private static string Reduce (string path)
1089 path = path.Replace ('\\','/');
1090 string [] parts = path.Split ('/');
1091 ArrayList result = new ArrayList ();
1093 int end = parts.Length;
1094 for (int i = 0; i < end; i++) {
1095 string current = parts [i];
1096 if (current == "" || current == "." )
1099 if (current == "..") {
1100 if (result.Count == 0) {
1101 if (i == 1) // see bug 52599
1103 throw new Exception ("Invalid path.");
1106 result.RemoveAt (result.Count - 1);
1110 result.Add (current);
1113 if (result.Count == 0)
1116 result.Insert (0, "");
1118 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1119 if (path.EndsWith ("/"))
1125 private struct UriScheme
1127 public string scheme;
1128 public string delimiter;
1129 public int defaultPort;
1131 public UriScheme (string s, string d, int p)
1139 static UriScheme [] schemes = new UriScheme [] {
1140 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1141 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1142 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1143 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1144 new UriScheme (UriSchemeMailto, ":", 25),
1145 new UriScheme (UriSchemeNews, ":", 119),
1146 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1147 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1150 internal static string GetSchemeDelimiter (string scheme)
1152 for (int i = 0; i < schemes.Length; i++)
1153 if (schemes [i].scheme == scheme)
1154 return schemes [i].delimiter;
1155 return Uri.SchemeDelimiter;
1158 internal static int GetDefaultPort (string scheme)
1160 for (int i = 0; i < schemes.Length; i++)
1161 if (schemes [i].scheme == scheme)
1162 return schemes [i].defaultPort;
1166 private string GetOpaqueWiseSchemeDelimiter ()
1171 return GetSchemeDelimiter (scheme);
1174 protected virtual bool IsBadFileSystemCharacter (char ch)
1176 // It does not always overlap with InvalidPathChars.
1177 int chInt = (int) ch;
1178 if (chInt < 32 || (chInt < 64 && chInt > 57))
1197 protected static bool IsExcludedCharacter (char ch)
1199 if (ch <= 32 || ch >= 127)
1202 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1203 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1204 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1210 private static bool IsPredefinedScheme (string scheme)
1227 protected virtual bool IsReservedCharacter (char ch)
1229 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1230 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||