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;
121 public Uri (Uri baseUri, string relativeUri)
122 : this (baseUri, relativeUri, false)
126 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
129 throw new NullReferenceException ("baseUri");
131 // See RFC 2396 Par 5.2 and Appendix C
133 userEscaped = dontEscape;
135 if (relativeUri == null)
136 throw new NullReferenceException ("relativeUri");
138 // Check Windows UNC (for // it is scheme/host separator)
139 if (relativeUri.StartsWith ("\\\\")) {
140 source = relativeUri;
149 int pos = relativeUri.IndexOf (':');
152 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
154 // pos2 < 0 ... e.g. mailto
155 // pos2 > pos ... to block ':' in query part
156 if (pos2 > pos || pos2 < 0) {
157 // equivalent to new Uri (relativeUri, dontEscape)
158 source = relativeUri;
168 this.scheme = baseUri.scheme;
169 this.host = baseUri.host;
170 this.port = baseUri.port;
171 this.userinfo = baseUri.userinfo;
172 this.isUnc = baseUri.isUnc;
173 this.isUnixFilePath = baseUri.isUnixFilePath;
174 this.isOpaquePart = baseUri.isOpaquePart;
176 if (relativeUri == String.Empty) {
177 this.path = baseUri.path;
178 this.query = baseUri.query;
179 this.fragment = baseUri.fragment;
184 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
185 pos = relativeUri.IndexOf ('#');
188 fragment = relativeUri.Substring (pos);
190 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
191 relativeUri = relativeUri.Substring (0, pos);
195 pos = relativeUri.IndexOf ('?');
197 query = relativeUri.Substring (pos);
199 query = EscapeString (query);
200 relativeUri = relativeUri.Substring (0, pos);
203 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
204 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
205 source = scheme + ':' + relativeUri;
215 path = EscapeString (path);
222 if (relativeUri.Length > 0 || query.Length > 0) {
223 pos = path.LastIndexOf ('/');
225 path = path.Substring (0, pos + 1);
228 if(relativeUri.Length == 0)
237 pos = path.IndexOf ("./", startIndex);
241 path = path.Remove (0, 2);
242 else if (path [pos - 1] != '.')
243 path = path.Remove (pos, 2);
245 startIndex = pos + 1;
249 if (path.Length > 1 &&
250 path [path.Length - 1] == '.' &&
251 path [path.Length - 2] == '/')
252 path = path.Remove (path.Length - 1, 1);
257 pos = path.IndexOf ("/../", startIndex);
264 int pos2 = path.LastIndexOf ('/', pos - 1);
266 startIndex = pos + 1;
268 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
269 path = path.Remove (pos2 + 1, pos - pos2 + 3);
271 startIndex = pos + 1;
276 if (path.Length > 3 && path.EndsWith ("/..")) {
277 pos = path.LastIndexOf ('/', path.Length - 4);
279 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
280 path = path.Remove (pos + 1, path.Length - pos - 1);
284 path = EscapeString (path);
289 public string AbsolutePath {
293 public string AbsoluteUri {
295 if (cachedAbsoluteUri == null) {
296 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
298 return cachedAbsoluteUri;
302 public string Authority {
304 return (GetDefaultPort (scheme) == port)
305 ? host : host + ":" + port;
309 public string Fragment {
310 get { return fragment; }
317 public UriHostNameType HostNameType {
319 UriHostNameType ret = CheckHostName (host);
320 if (ret != UriHostNameType.Unknown)
323 // looks it always returns Basic...
324 return UriHostNameType.Basic; //.Unknown;
328 public bool IsDefaultPort {
329 get { return GetDefaultPort (scheme) == port; }
333 get { return (scheme == UriSchemeFile); }
336 public bool IsLoopback {
338 if (host == String.Empty)
341 if (host == "loopback" || host == "localhost")
345 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
347 } catch (FormatException) {}
350 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
351 } catch (FormatException) {}
358 // rule: This should be true only if
359 // - uri string starts from "\\", or
360 // - uri string starts from "//" (Samba way)
361 get { return isUnc; }
364 public string LocalPath {
366 if (cachedLocalPath != null)
367 return cachedLocalPath;
371 bool windows = (path.Length > 3 && path [1] == ':' &&
372 (path [2] == '\\' || path [2] == '/'));
375 string p = Unescape (path);
376 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
377 cachedLocalPath = p.Replace ('/', '\\');
381 // support *nix and W32 styles
382 if (path.Length > 1 && path [1] == ':')
383 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
385 // LAMESPEC: ok, now we cannot determine
386 // if such URI like "file://foo/bar" is
387 // Windows UNC or unix file path, so
388 // they should be handled differently.
389 else if (System.IO.Path.DirectorySeparatorChar == '\\')
390 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
392 cachedLocalPath = Unescape (path);
394 if (cachedLocalPath == String.Empty)
395 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
396 return cachedLocalPath;
400 public string PathAndQuery {
401 get { return path + query; }
408 public string Query {
409 get { return query; }
412 public string Scheme {
413 get { return scheme; }
416 public string [] Segments {
418 if (segments != null)
422 segments = new string [0];
426 string [] parts = path.Split ('/');
428 bool endSlash = path.EndsWith ("/");
429 if (parts.Length > 0 && endSlash) {
430 string [] newParts = new string [parts.Length - 1];
431 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
436 if (IsFile && path.Length > 1 && path [1] == ':') {
437 string [] newParts = new string [parts.Length + 1];
438 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
440 parts [0] = path.Substring (0, 2);
445 int end = parts.Length;
447 if (i != end - 1 || endSlash)
455 public bool UserEscaped {
456 get { return userEscaped; }
459 public string UserInfo {
460 get { return userinfo; }
466 public static UriHostNameType CheckHostName (string name)
468 if (name == null || name.Length == 0)
469 return UriHostNameType.Unknown;
471 if (IsIPv4Address (name))
472 return UriHostNameType.IPv4;
474 if (IsDomainAddress (name))
475 return UriHostNameType.Dns;
478 IPv6Address.Parse (name);
479 return UriHostNameType.IPv6;
480 } catch (FormatException) {}
482 return UriHostNameType.Unknown;
485 internal static bool IsIPv4Address (string name)
487 string [] captures = name.Split (new char [] {'.'});
488 if (captures.Length != 4)
490 for (int i = 0; i < 4; i++) {
492 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
493 if (d < 0 || d > 255)
495 } catch (Exception) {
502 internal static bool IsDomainAddress (string name)
504 int len = name.Length;
507 for (int i = 0; i < len; i++) {
510 if (!Char.IsLetterOrDigit (c))
512 } else if (c == '.') {
514 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
524 [MonoTODO ("Find out what this should do")]
525 protected virtual void Canonicalize ()
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 [MonoTODO ("Find out what this should do")]
548 protected virtual void CheckSecurity ()
552 public override bool Equals (object comparant)
554 if (comparant == null)
557 Uri uri = comparant as Uri;
559 string s = comparant as String;
565 CultureInfo inv = CultureInfo.InvariantCulture;
566 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
567 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
568 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
569 (this.port == uri.port) &&
570 (this.path == uri.path) &&
571 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
574 public override int GetHashCode ()
576 if (cachedHashCode == 0)
577 cachedHashCode = scheme.GetHashCode ()
578 + userinfo.GetHashCode ()
579 + host.GetHashCode ()
581 + path.GetHashCode ()
582 + query.GetHashCode ();
583 return cachedHashCode;
586 public string GetLeftPart (UriPartial part)
590 case UriPartial.Scheme :
591 return scheme + GetOpaqueWiseSchemeDelimiter ();
592 case UriPartial.Authority :
593 if (host == String.Empty ||
594 scheme == Uri.UriSchemeMailto ||
595 scheme == Uri.UriSchemeNews)
598 StringBuilder s = new StringBuilder ();
600 s.Append (GetOpaqueWiseSchemeDelimiter ());
601 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
602 s.Append ('/'); // win32 file
603 if (userinfo.Length > 0)
604 s.Append (userinfo).Append ('@');
606 defaultPort = GetDefaultPort (scheme);
607 if ((port != -1) && (port != defaultPort))
608 s.Append (':').Append (port);
609 return s.ToString ();
610 case UriPartial.Path :
611 StringBuilder sb = new StringBuilder ();
613 sb.Append (GetOpaqueWiseSchemeDelimiter ());
614 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
615 sb.Append ('/'); // win32 file
616 if (userinfo.Length > 0)
617 sb.Append (userinfo).Append ('@');
619 defaultPort = GetDefaultPort (scheme);
620 if ((port != -1) && (port != defaultPort))
621 sb.Append (':').Append (port);
623 return sb.ToString ();
628 public static int FromHex (char digit)
630 if ('0' <= digit && digit <= '9') {
631 return (int) (digit - '0');
634 if ('a' <= digit && digit <= 'f')
635 return (int) (digit - 'a' + 10);
637 if ('A' <= digit && digit <= 'F')
638 return (int) (digit - 'A' + 10);
640 throw new ArgumentException ("digit");
643 public static string HexEscape (char character)
645 if (character > 255) {
646 throw new ArgumentOutOfRangeException ("character");
649 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
650 + hexUpperChars [((character & 0x0f))];
653 public static char HexUnescape (string pattern, ref int index)
656 throw new ArgumentException ("pattern");
658 if (index < 0 || index >= pattern.Length)
659 throw new ArgumentOutOfRangeException ("index");
661 if (!IsHexEncoding (pattern, index))
662 return pattern [index++];
665 int msb = FromHex (pattern [index++]);
666 int lsb = FromHex (pattern [index++]);
667 return (char) ((msb << 4) | lsb);
670 public static bool IsHexDigit (char digit)
672 return (('0' <= digit && digit <= '9') ||
673 ('a' <= digit && digit <= 'f') ||
674 ('A' <= digit && digit <= 'F'));
677 public static bool IsHexEncoding (string pattern, int index)
679 if ((index + 3) > pattern.Length)
682 return ((pattern [index++] == '%') &&
683 IsHexDigit (pattern [index++]) &&
684 IsHexDigit (pattern [index]));
687 public string MakeRelative (Uri toUri)
689 if ((this.Scheme != toUri.Scheme) ||
690 (this.Authority != toUri.Authority))
691 return toUri.ToString ();
693 if (this.path == toUri.path)
696 string [] segments = this.Segments;
697 string [] segments2 = toUri.Segments;
700 int max = Math.Min (segments.Length, segments2.Length);
702 if (segments [k] != segments2 [k])
705 string result = String.Empty;
706 for (int i = k + 1; i < segments.Length; i++)
708 for (int i = k; i < segments2.Length; i++)
709 result += segments2 [i];
714 public override string ToString ()
716 if (cachedToString != null)
717 return cachedToString;
718 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
719 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
720 return cachedToString;
723 void ISerializable.GetObjectData (SerializationInfo info,
724 StreamingContext context)
726 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
732 protected virtual void Escape ()
734 path = EscapeString (path);
737 protected static string EscapeString (string str)
739 return EscapeString (str, false, true, true);
742 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
747 byte [] data = Encoding.UTF8.GetBytes (str);
748 StringBuilder s = new StringBuilder ();
749 int len = data.Length;
750 for (int i = 0; i < len; i++) {
751 char c = (char) data [i];
752 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
753 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
754 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
755 // space = <US-ASCII coded character 20 hexadecimal>
756 // delims = "<" | ">" | "#" | "%" | <">
757 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
759 // check for escape code already placed in str,
760 // i.e. for encoding that follows the pattern
761 // "%hexhex" in a string, where "hex" is a digit from 0-9
762 // or a letter from A-F (case-insensitive).
763 if('%' == c && IsHexEncoding(str,i))
765 // if ,yes , copy it as is
772 if ((c <= 0x20) || (c >= 0x7f) ||
773 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
774 (escapeHex && (c == '#')) ||
775 (escapeBrackets && (c == '[' || c == ']')) ||
776 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
777 s.Append (HexEscape (c));
785 return s.ToString ();
788 // On .NET 1.x, this method is called from .ctor(). When overriden, we
789 // can avoid the "absolute uri" constraints of the .ctor() by
790 // overriding with custom code.
792 [Obsolete("The method has been deprecated. It is not used by the system.")]
794 protected virtual void Parse ()
801 private void ParseUri ()
808 host = EscapeString (host, false, true, false);
809 path = EscapeString (path);
812 protected virtual string Unescape (string str)
814 return Unescape (str, false);
817 private string Unescape (string str, bool excludeSharp)
821 StringBuilder s = new StringBuilder ();
822 int len = str.Length;
823 for (int i = 0; i < len; i++) {
827 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
828 if (excludeSharp && x == '#')
832 if (surrogate != char.MinValue)
833 s.Append (surrogate);
839 return s.ToString ();
845 private void ParseAsWindowsUNC (string uriString)
847 scheme = UriSchemeFile;
849 fragment = String.Empty;
850 query = String.Empty;
853 uriString = uriString.TrimStart (new char [] {'\\'});
854 int pos = uriString.IndexOf ('\\');
856 path = uriString.Substring (pos);
857 host = uriString.Substring (0, pos);
858 } else { // "\\\\server"
862 path = path.Replace ("\\", "/");
865 private void ParseAsWindowsAbsoluteFilePath (string uriString)
867 if (uriString.Length > 2 && uriString [2] != '\\'
868 && uriString [2] != '/')
869 throw new UriFormatException ("Relative file path is not allowed.");
870 scheme = UriSchemeFile;
873 path = uriString.Replace ("\\", "/");
874 fragment = String.Empty;
875 query = String.Empty;
878 private void ParseAsUnixAbsoluteFilePath (string uriString)
880 isUnixFilePath = true;
881 scheme = UriSchemeFile;
883 fragment = String.Empty;
884 query = String.Empty;
888 if (uriString.StartsWith ("//")) {
889 uriString = uriString.TrimStart (new char [] {'/'});
890 // Now we don't regard //foo/bar as "foo" host.
892 int pos = uriString.IndexOf ('/');
894 path = '/' + uriString.Substring (pos + 1);
895 host = uriString.Substring (0, pos);
896 } else { // "///server"
901 path = '/' + uriString;
907 // this parse method is as relaxed as possible about the format
908 // it will hardly ever throw a UriFormatException
909 private void Parse (string uriString)
914 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
918 if (uriString == null)
919 throw new ArgumentNullException ("uriString");
921 int len = uriString.Length;
923 throw new UriFormatException ();
928 // Identify Windows path, unix path, or standard URI.
929 pos = uriString.IndexOf (':');
931 throw new UriFormatException("Invalid URI: "
932 + "The format of the URI could not be "
934 } else if (pos < 0) {
935 // It must be Unix file path or Windows UNC
936 if (uriString [0] == '/')
937 ParseAsUnixAbsoluteFilePath (uriString);
938 else if (uriString.StartsWith ("\\\\"))
939 ParseAsWindowsUNC (uriString);
941 throw new UriFormatException ("URI scheme was not recognized, and input string was not recognized as an absolute file path.");
945 if (!Char.IsLetter (uriString [0]))
946 throw new UriFormatException ("URI scheme must start with a letter.");
947 // This means 'a:' == windows full path.
948 ParseAsWindowsAbsoluteFilePath (uriString);
953 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
954 // Check scheme name characters as specified in RFC2396.
955 if (!Char.IsLetter (scheme [0]))
956 throw new UriFormatException ("URI scheme must start with a letter.");
957 for (int i = 1; i < scheme.Length; i++) {
958 if (!Char.IsLetterOrDigit (scheme, i)) {
959 switch (scheme [i]) {
965 throw new UriFormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
969 uriString = uriString.Substring (pos + 1);
972 pos = uriString.IndexOf ('#');
973 if (!IsUnc && pos != -1) {
975 fragment = uriString.Substring (pos);
977 fragment = "#" + EscapeString (uriString.Substring (pos+1));
979 uriString = uriString.Substring (0, pos);
983 pos = uriString.IndexOf ('?');
985 query = uriString.Substring (pos);
986 uriString = uriString.Substring (0, pos);
988 query = EscapeString (query);
992 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
993 (uriString.Length < 2) ||
994 (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] != '/')))
995 throw new UriFormatException ("Invalid URI: The Authority/Host could not be parsed.");
998 bool unixAbsPath = scheme == UriSchemeFile && (uriString.StartsWith ("///") || uriString == "//");
999 if (uriString.StartsWith ("//")) {
1000 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1001 uriString = uriString.Substring (2);
1003 if (scheme == UriSchemeFile) {
1004 int num_leading_slash = 2;
1005 for (int i = 0; i < uriString.Length; i++) {
1006 if (uriString [i] != '/')
1008 num_leading_slash++;
1010 if (num_leading_slash >= 4) {
1011 unixAbsPath = false;
1012 uriString = uriString.TrimStart ('/');
1013 } else if (num_leading_slash >= 3) {
1014 uriString = uriString.Substring (1);
1018 if (uriString.Length > 1 && uriString [1] == ':')
1019 unixAbsPath = false;
1021 } else if (!IsPredefinedScheme (scheme)) {
1023 isOpaquePart = true;
1028 pos = uriString.IndexOf ('/');
1032 if ((scheme != Uri.UriSchemeMailto) &&
1033 (scheme != Uri.UriSchemeNews) &&
1034 (scheme != Uri.UriSchemeFile))
1037 path = uriString.Substring (pos);
1038 uriString = uriString.Substring (0, pos);
1042 pos = uriString.IndexOf ("@");
1044 userinfo = uriString.Substring (0, pos);
1045 uriString = uriString.Remove (0, pos + 1);
1050 pos = uriString.LastIndexOf (":");
1053 if (pos != -1 && pos != (uriString.Length - 1)) {
1054 string portStr = uriString.Remove (0, pos + 1);
1055 if (portStr.Length > 1 && portStr[portStr.Length - 1] != ']') {
1058 port = (int) UInt16.Parse (portStr, CultureInfo.InvariantCulture);
1060 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1062 uriString = uriString.Substring (0, pos);
1063 } catch (Exception) {
1064 throw new UriFormatException ("Invalid URI: Invalid port number");
1068 port = GetDefaultPort (scheme);
1073 port = GetDefaultPort (scheme);
1079 if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1081 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1082 } catch (Exception) {
1083 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1088 path = '/' + uriString;
1089 host = String.Empty;
1090 } else if (host.Length == 2 && host [1] == ':') {
1093 host = String.Empty;
1094 } else if (isUnixFilePath) {
1095 uriString = "//" + uriString;
1096 host = String.Empty;
1097 } else if (scheme == UriSchemeFile) {
1099 } else if (host.Length == 0 &&
1100 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp
1101 || scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1102 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1105 if ((scheme != Uri.UriSchemeMailto) &&
1106 (scheme != Uri.UriSchemeNews) &&
1107 (scheme != Uri.UriSchemeFile))
1108 path = Reduce (path);
1111 private static string Reduce (string path)
1113 path = path.Replace ('\\','/');
1114 string [] parts = path.Split ('/');
1115 ArrayList result = new ArrayList ();
1117 int end = parts.Length;
1118 for (int i = 0; i < end; i++) {
1119 string current = parts [i];
1120 if (current == "" || current == "." )
1123 if (current == "..") {
1124 int resultCount = result.Count;
1126 // in 2.0 profile, skip leading ".." parts
1127 if (resultCount == 0) {
1131 result.RemoveAt (resultCount - 1);
1134 // in 1.x profile, retain leading ".." parts, and only reduce
1135 // URI is previous part is not ".."
1136 if (resultCount > 0) {
1137 if ((string) result[resultCount - 1] != "..") {
1138 result.RemoveAt (resultCount - 1);
1145 result.Add (current);
1148 if (result.Count == 0)
1151 result.Insert (0, "");
1153 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1154 if (path.EndsWith ("/"))
1160 // A variant of HexUnescape() which can decode multi-byte escaped
1161 // sequences such as (e.g.) %E3%81%8B into a single character
1162 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1164 surrogate = char.MinValue;
1166 if (pattern == null)
1167 throw new ArgumentException ("pattern");
1169 if (index < 0 || index >= pattern.Length)
1170 throw new ArgumentOutOfRangeException ("index");
1172 if (!IsHexEncoding (pattern, index))
1173 return pattern [index++];
1175 int orig_index = index++;
1176 int msb = FromHex (pattern [index++]);
1177 int lsb = FromHex (pattern [index++]);
1179 // We might be dealing with a multi-byte character:
1180 // The number of ones at the top-end of the first byte will tell us
1181 // how many bytes will make up this character.
1184 while ((msb_copy & 0x8) == 0x8) {
1189 // We might be dealing with a single-byte character:
1190 // If there was only 0 or 1 leading ones then we're not dealing
1191 // with a multi-byte character.
1193 return (char) ((msb << 4) | lsb);
1195 // Now that we know how many bytes *should* follow, we'll check them
1196 // to ensure we are dealing with a valid multi-byte character.
1197 byte [] chars = new byte [num_bytes];
1198 bool all_invalid = false;
1199 chars[0] = (byte) ((msb << 4) | lsb);
1201 for (int i = 1; i < num_bytes; i++) {
1202 if (!IsHexEncoding (pattern, index++)) {
1207 // All following bytes must be in the form 10xxxxxx
1208 int cur_msb = FromHex (pattern [index++]);
1209 if ((cur_msb & 0xc) != 0x8) {
1214 int cur_lsb = FromHex (pattern [index++]);
1215 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1218 // If what looked like a multi-byte character is invalid, then we'll
1219 // just return the first byte as a single byte character.
1221 index = orig_index + 3;
1222 return (char) chars[0];
1225 // Otherwise, we're dealing with a valid multi-byte character.
1226 // We need to ignore the leading ones from the first byte:
1227 byte mask = (byte) 0xFF;
1228 mask >>= (num_bytes + 1);
1229 int result = chars[0] & mask;
1231 // The result will now be built up from the following bytes.
1232 for (int i = 1; i < num_bytes; i++) {
1233 // Ignore upper two bits
1235 result |= (chars[i] & 0x3F);
1238 if (result <= 0xFFFF) {
1239 return (char) result;
1241 // We need to handle this as a UTF16 surrogate (i.e. return
1244 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1245 return (char) ((result >> 10) | 0xD800);
1249 private struct UriScheme
1251 public string scheme;
1252 public string delimiter;
1253 public int defaultPort;
1255 public UriScheme (string s, string d, int p)
1263 static UriScheme [] schemes = new UriScheme [] {
1264 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1265 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1266 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1267 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1268 new UriScheme (UriSchemeMailto, ":", 25),
1269 new UriScheme (UriSchemeNews, ":", 119),
1270 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1271 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1274 internal static string GetSchemeDelimiter (string scheme)
1276 for (int i = 0; i < schemes.Length; i++)
1277 if (schemes [i].scheme == scheme)
1278 return schemes [i].delimiter;
1279 return Uri.SchemeDelimiter;
1282 internal static int GetDefaultPort (string scheme)
1284 for (int i = 0; i < schemes.Length; i++)
1285 if (schemes [i].scheme == scheme)
1286 return schemes [i].defaultPort;
1290 private string GetOpaqueWiseSchemeDelimiter ()
1295 return GetSchemeDelimiter (scheme);
1298 protected virtual bool IsBadFileSystemCharacter (char ch)
1300 // It does not always overlap with InvalidPathChars.
1301 int chInt = (int) ch;
1302 if (chInt < 32 || (chInt < 64 && chInt > 57))
1321 protected static bool IsExcludedCharacter (char ch)
1323 if (ch <= 32 || ch >= 127)
1326 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1327 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1328 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1334 private static bool IsPredefinedScheme (string scheme)
1351 protected virtual bool IsReservedCharacter (char ch)
1353 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1354 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||