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)
10 // Sebastien Pouliot <sebastien@ximian.com>
12 // (C) 2001 Garrett Rooney
13 // (C) 2003 Ian MacLean
14 // (C) 2003 Ben Maurer
15 // Copyright (C) 2003,2005 Novell, Inc (http://www.novell.com)
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.ComponentModel;
40 using System.Runtime.Serialization;
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
53 [TypeConverter (typeof (UriTypeConverter))]
54 public class Uri : ISerializable {
56 public class Uri : MarshalByRefObject, ISerializable {
59 // o scheme excludes the scheme delimiter
60 // o port is -1 to indicate no port is defined
61 // o path is empty or starts with / when scheme delimiter == "://"
62 // o query is empty or starts with ? char, escaped.
63 // o fragment is empty or starts with # char, unescaped.
64 // o all class variables are in escaped format when they are escapable,
65 // except cachedToString.
66 // o UNC is supported, as starts with "\\" for windows,
69 private bool isUnixFilePath;
70 private string source;
71 private string scheme = String.Empty;
72 private string host = String.Empty;
73 private int port = -1;
74 private string path = String.Empty;
75 private string query = String.Empty;
76 private string fragment = String.Empty;
77 private string userinfo = String.Empty;
79 private bool isOpaquePart;
81 private string [] segments;
83 private bool userEscaped;
84 private string cachedAbsoluteUri;
85 private string cachedToString;
86 private string cachedLocalPath;
87 private int cachedHashCode;
89 private static readonly string hexUpperChars = "0123456789ABCDEF";
93 public static readonly string SchemeDelimiter = "://";
94 public static readonly string UriSchemeFile = "file";
95 public static readonly string UriSchemeFtp = "ftp";
96 public static readonly string UriSchemeGopher = "gopher";
97 public static readonly string UriSchemeHttp = "http";
98 public static readonly string UriSchemeHttps = "https";
99 public static readonly string UriSchemeMailto = "mailto";
100 public static readonly string UriSchemeNews = "news";
101 public static readonly string UriSchemeNntp = "nntp";
103 public static readonly string UriSchemeNetPipe = "net.pipe";
104 public static readonly string UriSchemeNetTcp = "net.tcp";
109 public Uri (string uriString) : this (uriString, false)
113 protected Uri (SerializationInfo serializationInfo,
114 StreamingContext streamingContext) :
115 this (serializationInfo.GetString ("AbsoluteUri"), true)
120 public Uri (string uriString, UriKind uriKind)
126 case UriKind.Absolute:
128 throw new InvalidOperationException (Locale.GetText ("This isn't an absolute URI."));
130 case UriKind.Relative:
132 throw new InvalidOperationException (Locale.GetText ("This isn't an relative URI."));
135 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
136 throw new ArgumentException ("uriKind", msg);
140 public Uri (Uri baseUri, Uri relativeUri)
141 : this (baseUri, relativeUri.OriginalString, false)
143 // FIXME: this should call UriParser.Resolve
146 // note: doc says that dontEscape is always false but tests show otherwise
148 public Uri (string uriString, bool dontEscape)
150 userEscaped = dontEscape;
155 public Uri (string uriString, bool dontEscape)
157 userEscaped = dontEscape;
163 public Uri (Uri baseUri, string relativeUri)
164 : this (baseUri, relativeUri, false)
166 // FIXME: this should call UriParser.Resolve
170 [Obsolete ("dontEscape is always false")]
172 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
176 throw new ArgumentNullException ("baseUri");
177 if (relativeUri == null)
178 relativeUri = String.Empty;
181 throw new NullReferenceException ("baseUri");
183 // See RFC 2396 Par 5.2 and Appendix C
185 userEscaped = dontEscape;
187 // Check Windows UNC (for // it is scheme/host separator)
188 if (relativeUri.StartsWith ("\\\\")) {
189 source = relativeUri;
198 int pos = relativeUri.IndexOf (':');
201 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
203 // pos2 < 0 ... e.g. mailto
204 // pos2 > pos ... to block ':' in query part
205 if (pos2 > pos || pos2 < 0) {
206 // equivalent to new Uri (relativeUri, dontEscape)
207 source = relativeUri;
217 this.scheme = baseUri.scheme;
218 this.host = baseUri.host;
219 this.port = baseUri.port;
220 this.userinfo = baseUri.userinfo;
221 this.isUnc = baseUri.isUnc;
222 this.isUnixFilePath = baseUri.isUnixFilePath;
223 this.isOpaquePart = baseUri.isOpaquePart;
225 if (relativeUri == String.Empty) {
226 this.path = baseUri.path;
227 this.query = baseUri.query;
228 this.fragment = baseUri.fragment;
233 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
234 pos = relativeUri.IndexOf ('#');
237 fragment = relativeUri.Substring (pos);
239 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
240 relativeUri = relativeUri.Substring (0, pos);
244 pos = relativeUri.IndexOf ('?');
246 query = relativeUri.Substring (pos);
248 query = EscapeString (query);
249 relativeUri = relativeUri.Substring (0, pos);
252 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
253 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
254 source = scheme + ':' + relativeUri;
264 path = EscapeString (path);
271 if (relativeUri.Length > 0 || query.Length > 0) {
272 pos = path.LastIndexOf ('/');
274 path = path.Substring (0, pos + 1);
277 if(relativeUri.Length == 0)
286 pos = path.IndexOf ("./", startIndex);
290 path = path.Remove (0, 2);
291 else if (path [pos - 1] != '.')
292 path = path.Remove (pos, 2);
294 startIndex = pos + 1;
298 if (path.Length > 1 &&
299 path [path.Length - 1] == '.' &&
300 path [path.Length - 2] == '/')
301 path = path.Remove (path.Length - 1, 1);
306 pos = path.IndexOf ("/../", startIndex);
313 int pos2 = path.LastIndexOf ('/', pos - 1);
315 startIndex = pos + 1;
317 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
318 path = path.Remove (pos2 + 1, pos - pos2 + 3);
320 startIndex = pos + 1;
325 if (path.Length > 3 && path.EndsWith ("/..")) {
326 pos = path.LastIndexOf ('/', path.Length - 4);
328 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
329 path = path.Remove (pos + 1, path.Length - pos - 1);
333 path = EscapeString (path);
338 public string AbsolutePath {
344 // faster (mailto) and special (file) cases
347 if (path.Length == 0) {
348 string start = Scheme + SchemeDelimiter;
349 if (path.StartsWith (start))
362 public string AbsoluteUri {
364 if (cachedAbsoluteUri == null) {
365 cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
366 if (query.Length > 0)
367 cachedAbsoluteUri += query;
368 if (fragment.Length > 0)
369 cachedAbsoluteUri += fragment;
371 return cachedAbsoluteUri;
375 public string Authority {
377 return (GetDefaultPort (scheme) == port)
378 ? host : host + ":" + port;
382 public string Fragment {
383 get { return fragment; }
390 public UriHostNameType HostNameType {
392 UriHostNameType ret = CheckHostName (host);
393 if (ret != UriHostNameType.Unknown)
398 return UriHostNameType.Basic;
400 return (IsFile) ? UriHostNameType.Basic : ret;
403 // looks it always returns Basic...
404 return UriHostNameType.Basic; //.Unknown;
409 public bool IsDefaultPort {
410 get { return GetDefaultPort (scheme) == port; }
414 get { return (scheme == UriSchemeFile); }
417 public bool IsLoopback {
419 if (host.Length == 0) {
427 if (host == "loopback" || host == "localhost")
431 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
433 } catch (FormatException) {}
436 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
437 } catch (FormatException) {}
444 // rule: This should be true only if
445 // - uri string starts from "\\", or
446 // - uri string starts from "//" (Samba way)
447 get { return isUnc; }
450 public string LocalPath {
452 if (cachedLocalPath != null)
453 return cachedLocalPath;
457 bool windows = (path.Length > 3 && path [1] == ':' &&
458 (path [2] == '\\' || path [2] == '/'));
461 string p = Unescape (path);
462 bool replace = windows;
464 replace |= (System.IO.Path.DirectorySeparatorChar == '\\');
467 cachedLocalPath = p.Replace ('/', '\\');
471 // support *nix and W32 styles
472 if (path.Length > 1 && path [1] == ':')
473 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
475 // LAMESPEC: ok, now we cannot determine
476 // if such URI like "file://foo/bar" is
477 // Windows UNC or unix file path, so
478 // they should be handled differently.
479 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
481 if (path.Length > 0) {
483 if ((path.Length > 1) || (path[0] != '/')) {
484 h += path.Replace ('/', '\\');
487 h += path.Replace ('/', '\\');
490 cachedLocalPath = "\\\\" + Unescape (h);
492 cachedLocalPath = Unescape (path);
494 if (cachedLocalPath.Length == 0)
495 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
496 return cachedLocalPath;
500 public string PathAndQuery {
501 get { return path + query; }
508 public string Query {
509 get { return query; }
512 public string Scheme {
513 get { return scheme; }
516 public string [] Segments {
518 if (segments != null)
521 if (path.Length == 0) {
522 segments = new string [0];
526 string [] parts = path.Split ('/');
528 bool endSlash = path.EndsWith ("/");
529 if (parts.Length > 0 && endSlash) {
530 string [] newParts = new string [parts.Length - 1];
531 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
536 if (IsFile && path.Length > 1 && path [1] == ':') {
537 string [] newParts = new string [parts.Length + 1];
538 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
540 parts [0] = path.Substring (0, 2);
545 int end = parts.Length;
547 if (i != end - 1 || endSlash)
555 public bool UserEscaped {
556 get { return userEscaped; }
559 public string UserInfo {
560 get { return userinfo; }
564 [MonoTODO ("add support for IPv6 address")]
565 public string DnsSafeHost {
568 throw new InvalidOperationException (Locale.GetText ("This isn't an absolute URI."));
569 return Unescape (host);
574 public bool IsAbsoluteUri {
578 public string OriginalString {
581 throw new InvalidOperationException (Locale.GetText ("This isn't an absolute URI."));
589 public static UriHostNameType CheckHostName (string name)
591 if (name == null || name.Length == 0)
592 return UriHostNameType.Unknown;
594 if (IsIPv4Address (name))
595 return UriHostNameType.IPv4;
597 if (IsDomainAddress (name))
598 return UriHostNameType.Dns;
601 IPv6Address.Parse (name);
602 return UriHostNameType.IPv6;
603 } catch (FormatException) {}
605 return UriHostNameType.Unknown;
608 internal static bool IsIPv4Address (string name)
610 string [] captures = name.Split (new char [] {'.'});
611 if (captures.Length != 4)
613 for (int i = 0; i < 4; i++) {
615 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
616 if (d < 0 || d > 255)
618 } catch (Exception) {
625 internal static bool IsDomainAddress (string name)
627 int len = name.Length;
630 for (int i = 0; i < len; i++) {
633 if (!Char.IsLetterOrDigit (c))
635 } else if (c == '.') {
637 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
647 [MonoTODO ("Find out what this should do")]
651 protected virtual void Canonicalize ()
655 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
656 public static bool CheckSchemeName (string schemeName)
658 if (schemeName == null || schemeName.Length == 0)
661 if (!IsAlpha (schemeName [0]))
664 int len = schemeName.Length;
665 for (int i = 1; i < len; i++) {
666 char c = schemeName [i];
667 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
674 private static bool IsAlpha (char c)
677 // as defined in rfc2234
678 // %x41-5A / %x61-7A (A-Z / a-z)
680 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
682 // Fx 1.x got this too large
683 return Char.IsLetter (c);
687 [MonoTODO ("Find out what this should do")]
691 protected virtual void CheckSecurity ()
695 public override bool Equals (object comparant)
697 if (comparant == null)
700 Uri uri = comparant as Uri;
702 string s = comparant as String;
708 CultureInfo inv = CultureInfo.InvariantCulture;
709 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
710 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
711 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
712 (this.port == uri.port) &&
713 (this.path == uri.path) &&
714 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
717 public override int GetHashCode ()
719 if (cachedHashCode == 0)
720 cachedHashCode = scheme.GetHashCode ()
721 + userinfo.GetHashCode ()
722 + host.GetHashCode ()
724 + path.GetHashCode ()
725 + query.GetHashCode ();
726 return cachedHashCode;
729 public string GetLeftPart (UriPartial part)
733 case UriPartial.Scheme :
734 return scheme + GetOpaqueWiseSchemeDelimiter ();
735 case UriPartial.Authority :
736 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
739 StringBuilder s = new StringBuilder ();
741 s.Append (GetOpaqueWiseSchemeDelimiter ());
742 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
743 s.Append ('/'); // win32 file
744 if (userinfo.Length > 0)
745 s.Append (userinfo).Append ('@');
747 defaultPort = GetDefaultPort (scheme);
748 if ((port != -1) && (port != defaultPort))
749 s.Append (':').Append (port);
750 return s.ToString ();
751 case UriPartial.Path :
752 StringBuilder sb = new StringBuilder ();
754 sb.Append (GetOpaqueWiseSchemeDelimiter ());
755 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
756 sb.Append ('/'); // win32 file
757 if (userinfo.Length > 0)
758 sb.Append (userinfo).Append ('@');
760 defaultPort = GetDefaultPort (scheme);
761 if ((port != -1) && (port != defaultPort))
762 sb.Append (':').Append (port);
764 if (path.Length > 0) {
772 sb.Append (Reduce (path));
779 return sb.ToString ();
784 public static int FromHex (char digit)
786 if ('0' <= digit && digit <= '9') {
787 return (int) (digit - '0');
790 if ('a' <= digit && digit <= 'f')
791 return (int) (digit - 'a' + 10);
793 if ('A' <= digit && digit <= 'F')
794 return (int) (digit - 'A' + 10);
796 throw new ArgumentException ("digit");
799 public static string HexEscape (char character)
801 if (character > 255) {
802 throw new ArgumentOutOfRangeException ("character");
805 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
806 + hexUpperChars [((character & 0x0f))];
809 public static char HexUnescape (string pattern, ref int index)
812 throw new ArgumentException ("pattern");
814 if (index < 0 || index >= pattern.Length)
815 throw new ArgumentOutOfRangeException ("index");
817 if (!IsHexEncoding (pattern, index))
818 return pattern [index++];
821 int msb = FromHex (pattern [index++]);
822 int lsb = FromHex (pattern [index++]);
823 return (char) ((msb << 4) | lsb);
826 public static bool IsHexDigit (char digit)
828 return (('0' <= digit && digit <= '9') ||
829 ('a' <= digit && digit <= 'f') ||
830 ('A' <= digit && digit <= 'F'));
833 public static bool IsHexEncoding (string pattern, int index)
835 if ((index + 3) > pattern.Length)
838 return ((pattern [index++] == '%') &&
839 IsHexDigit (pattern [index++]) &&
840 IsHexDigit (pattern [index]));
846 public string MakeRelative (Uri toUri)
848 if ((this.Scheme != toUri.Scheme) ||
849 (this.Authority != toUri.Authority))
850 return toUri.ToString ();
852 if (this.path == toUri.path)
855 string [] segments = this.Segments;
856 string [] segments2 = toUri.Segments;
859 int max = Math.Min (segments.Length, segments2.Length);
861 if (segments [k] != segments2 [k])
864 string result = String.Empty;
865 for (int i = k + 1; i < segments.Length; i++)
867 for (int i = k; i < segments2.Length; i++)
868 result += segments2 [i];
873 public override string ToString ()
875 if (cachedToString != null)
876 return cachedToString;
878 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
879 if (query.Length > 0) {
880 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1), true) : Unescape (query, true);
883 if (fragment.Length > 0)
884 cachedToString += fragment;
885 return cachedToString;
889 protected void GetObjectData (SerializationInfo info, StreamingContext context)
891 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
895 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
897 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
906 protected virtual void Escape ()
908 path = EscapeString (path);
914 protected static string EscapeString (string str)
916 return EscapeString (str, false, true, true);
919 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
924 byte [] data = Encoding.UTF8.GetBytes (str);
925 StringBuilder s = new StringBuilder ();
926 int len = data.Length;
927 for (int i = 0; i < len; i++) {
928 char c = (char) data [i];
929 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
930 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
931 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
932 // space = <US-ASCII coded character 20 hexadecimal>
933 // delims = "<" | ">" | "#" | "%" | <">
934 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
936 // check for escape code already placed in str,
937 // i.e. for encoding that follows the pattern
938 // "%hexhex" in a string, where "hex" is a digit from 0-9
939 // or a letter from A-F (case-insensitive).
940 if('%' == c && IsHexEncoding(str,i))
942 // if ,yes , copy it as is
949 if ((c <= 0x20) || (c >= 0x7f) ||
950 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
951 (escapeHex && (c == '#')) ||
952 (escapeBrackets && (c == '[' || c == ']')) ||
953 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
954 s.Append (HexEscape (c));
962 return s.ToString ();
965 // On .NET 1.x, this method is called from .ctor(). When overriden, we
966 // can avoid the "absolute uri" constraints of the .ctor() by
967 // overriding with custom code.
969 [Obsolete("The method has been deprecated. It is not used by the system.")]
971 protected virtual void Parse ()
978 private void ParseUri ()
985 host = EscapeString (host, false, true, false);
986 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
987 // host name present (but not an IPv6 address)
988 host = host.ToLower (CultureInfo.InvariantCulture);
991 if (path.Length > 0) {
992 path = EscapeString (path);
999 protected virtual string Unescape (string str)
1001 return Unescape (str, false);
1004 internal static string Unescape (string str, bool excludeSpecial)
1007 return String.Empty;
1008 StringBuilder s = new StringBuilder ();
1009 int len = str.Length;
1010 for (int i = 0; i < len; i++) {
1014 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1015 if (excludeSpecial && x == '#')
1017 else if (excludeSpecial && x == '%')
1019 else if (excludeSpecial && x == '?')
1023 if (surrogate != char.MinValue)
1024 s.Append (surrogate);
1030 return s.ToString ();
1036 private void ParseAsWindowsUNC (string uriString)
1038 scheme = UriSchemeFile;
1040 fragment = String.Empty;
1041 query = String.Empty;
1044 uriString = uriString.TrimStart (new char [] {'\\'});
1045 int pos = uriString.IndexOf ('\\');
1047 path = uriString.Substring (pos);
1048 host = uriString.Substring (0, pos);
1049 } else { // "\\\\server"
1051 path = String.Empty;
1053 path = path.Replace ("\\", "/");
1056 private void ParseAsWindowsAbsoluteFilePath (string uriString)
1058 if (uriString.Length > 2 && uriString [2] != '\\'
1059 && uriString [2] != '/')
1060 throw new UriFormatException ("Relative file path is not allowed.");
1061 scheme = UriSchemeFile;
1062 host = String.Empty;
1064 path = uriString.Replace ("\\", "/");
1065 fragment = String.Empty;
1066 query = String.Empty;
1069 private void ParseAsUnixAbsoluteFilePath (string uriString)
1071 isUnixFilePath = true;
1072 scheme = UriSchemeFile;
1074 fragment = String.Empty;
1075 query = String.Empty;
1076 host = String.Empty;
1079 if (uriString.StartsWith ("//")) {
1080 uriString = uriString.TrimStart (new char [] {'/'});
1081 // Now we don't regard //foo/bar as "foo" host.
1083 int pos = uriString.IndexOf ('/');
1085 path = '/' + uriString.Substring (pos + 1);
1086 host = uriString.Substring (0, pos);
1087 } else { // "///server"
1089 path = String.Empty;
1092 path = '/' + uriString;
1098 // this parse method is as relaxed as possible about the format
1099 // it will hardly ever throw a UriFormatException
1100 private void Parse (string uriString)
1105 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1109 if (uriString == null)
1110 throw new ArgumentNullException ("uriString");
1112 int len = uriString.Length;
1114 throw new UriFormatException ();
1119 // Identify Windows path, unix path, or standard URI.
1120 pos = uriString.IndexOf (':');
1122 throw new UriFormatException("Invalid URI: "
1123 + "The format of the URI could not be "
1125 } else if (pos < 0) {
1126 // It must be Unix file path or Windows UNC
1127 if (uriString [0] == '/')
1128 ParseAsUnixAbsoluteFilePath (uriString);
1129 else if (uriString.StartsWith ("\\\\"))
1130 ParseAsWindowsUNC (uriString);
1132 throw new UriFormatException ("URI scheme was not recognized, and input string was not recognized as an absolute file path.");
1135 else if (pos == 1) {
1136 if (!IsAlpha (uriString [0]))
1137 throw new UriFormatException ("URI scheme must start with a letter.");
1138 // This means 'a:' == windows full path.
1139 ParseAsWindowsAbsoluteFilePath (uriString);
1144 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1145 // Check scheme name characters as specified in RFC2396.
1146 // Note: different checks in 1.x and 2.0
1147 if (!CheckSchemeName (scheme)) {
1148 string msg = Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
1149 throw new UriFormatException (msg);
1152 uriString = uriString.Substring (pos + 1);
1155 pos = uriString.IndexOf ('#');
1156 if (!IsUnc && pos != -1) {
1158 fragment = uriString.Substring (pos);
1160 fragment = "#" + EscapeString (uriString.Substring (pos+1));
1162 uriString = uriString.Substring (0, pos);
1166 pos = uriString.IndexOf ('?');
1168 query = uriString.Substring (pos);
1169 uriString = uriString.Substring (0, pos);
1171 query = EscapeString (query);
1175 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
1176 (uriString.Length < 2) ||
1177 (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] != '/')))
1178 throw new UriFormatException ("Invalid URI: The Authority/Host could not be parsed.");
1181 bool unixAbsPath = scheme == UriSchemeFile && (uriString.StartsWith ("///") || uriString == "//");
1182 if (uriString.StartsWith ("//")) {
1183 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1184 uriString = uriString.Substring (2);
1186 if (scheme == UriSchemeFile) {
1187 int num_leading_slash = 2;
1188 for (int i = 0; i < uriString.Length; i++) {
1189 if (uriString [i] != '/')
1191 num_leading_slash++;
1193 if (num_leading_slash >= 4) {
1194 unixAbsPath = false;
1195 uriString = uriString.TrimStart ('/');
1196 } else if (num_leading_slash >= 3) {
1197 uriString = uriString.Substring (1);
1201 if (uriString.Length > 1 && uriString [1] == ':')
1202 unixAbsPath = false;
1204 } else if (!IsPredefinedScheme (scheme)) {
1206 isOpaquePart = true;
1211 pos = uriString.IndexOf ('/');
1215 if ((scheme != Uri.UriSchemeMailto) &&
1217 (scheme != Uri.UriSchemeFile) &&
1219 (scheme != Uri.UriSchemeNews))
1222 path = uriString.Substring (pos);
1223 uriString = uriString.Substring (0, pos);
1227 pos = uriString.IndexOf ("@");
1229 userinfo = uriString.Substring (0, pos);
1230 uriString = uriString.Remove (0, pos + 1);
1235 pos = uriString.LastIndexOf (":");
1238 if (pos != -1 && pos != (uriString.Length - 1)) {
1239 string portStr = uriString.Remove (0, pos + 1);
1240 if (portStr.Length > 1 && portStr[portStr.Length - 1] != ']') {
1243 port = (int) UInt16.Parse (portStr, CultureInfo.InvariantCulture);
1245 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1247 uriString = uriString.Substring (0, pos);
1248 } catch (Exception) {
1249 throw new UriFormatException ("Invalid URI: Invalid port number");
1253 port = GetDefaultPort (scheme);
1258 port = GetDefaultPort (scheme);
1266 path = '/' + uriString;
1267 host = String.Empty;
1268 } else if (host.Length == 2 && host [1] == ':') {
1271 host = String.Empty;
1272 } else if (isUnixFilePath) {
1273 uriString = "//" + uriString;
1274 host = String.Empty;
1275 } else if (scheme == UriSchemeFile) {
1277 } else if (scheme == UriSchemeNews) {
1278 // no host for 'news', misinterpreted path
1279 if (host.Length > 0) {
1281 host = String.Empty;
1283 } else if (host.Length == 0 &&
1284 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp
1285 || scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1286 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1289 bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
1290 if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
1292 host = "[" + IPv6Address.Parse (host).ToString (true) + "]";
1298 if (badhost && (scheme != "monodoc")) {
1299 string msg = Locale.GetText ("Invalid URI: The hostname could not be parsed.");
1300 throw new UriFormatException (msg);
1303 if ((scheme != Uri.UriSchemeMailto) &&
1304 (scheme != Uri.UriSchemeNews) &&
1305 (scheme != Uri.UriSchemeFile)) {
1306 path = Reduce (path);
1310 private static string Reduce (string path)
1312 path = path.Replace ('\\','/');
1313 string [] parts = path.Split ('/');
1314 ArrayList result = new ArrayList ();
1316 int end = parts.Length;
1317 for (int i = 0; i < end; i++) {
1318 string current = parts [i];
1319 if (current == "" || current == "." )
1322 if (current == "..") {
1323 int resultCount = result.Count;
1325 // in 2.0 profile, skip leading ".." parts
1326 if (resultCount == 0) {
1330 result.RemoveAt (resultCount - 1);
1333 // in 1.x profile, retain leading ".." parts, and only reduce
1334 // URI is previous part is not ".."
1335 if (resultCount > 0) {
1336 if ((string) result[resultCount - 1] != "..") {
1337 result.RemoveAt (resultCount - 1);
1344 result.Add (current);
1347 if (result.Count == 0)
1350 if (path [0] == '/')
1351 result.Insert (0, "");
1353 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1354 if (path.EndsWith ("/"))
1360 // A variant of HexUnescape() which can decode multi-byte escaped
1361 // sequences such as (e.g.) %E3%81%8B into a single character
1362 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1364 surrogate = char.MinValue;
1366 if (pattern == null)
1367 throw new ArgumentException ("pattern");
1369 if (index < 0 || index >= pattern.Length)
1370 throw new ArgumentOutOfRangeException ("index");
1372 if (!IsHexEncoding (pattern, index))
1373 return pattern [index++];
1375 int orig_index = index++;
1376 int msb = FromHex (pattern [index++]);
1377 int lsb = FromHex (pattern [index++]);
1379 // We might be dealing with a multi-byte character:
1380 // The number of ones at the top-end of the first byte will tell us
1381 // how many bytes will make up this character.
1384 while ((msb_copy & 0x8) == 0x8) {
1389 // We might be dealing with a single-byte character:
1390 // If there was only 0 or 1 leading ones then we're not dealing
1391 // with a multi-byte character.
1393 return (char) ((msb << 4) | lsb);
1395 // Now that we know how many bytes *should* follow, we'll check them
1396 // to ensure we are dealing with a valid multi-byte character.
1397 byte [] chars = new byte [num_bytes];
1398 bool all_invalid = false;
1399 chars[0] = (byte) ((msb << 4) | lsb);
1401 for (int i = 1; i < num_bytes; i++) {
1402 if (!IsHexEncoding (pattern, index++)) {
1407 // All following bytes must be in the form 10xxxxxx
1408 int cur_msb = FromHex (pattern [index++]);
1409 if ((cur_msb & 0xc) != 0x8) {
1414 int cur_lsb = FromHex (pattern [index++]);
1415 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1418 // If what looked like a multi-byte character is invalid, then we'll
1419 // just return the first byte as a single byte character.
1421 index = orig_index + 3;
1422 return (char) chars[0];
1425 // Otherwise, we're dealing with a valid multi-byte character.
1426 // We need to ignore the leading ones from the first byte:
1427 byte mask = (byte) 0xFF;
1428 mask >>= (num_bytes + 1);
1429 int result = chars[0] & mask;
1431 // The result will now be built up from the following bytes.
1432 for (int i = 1; i < num_bytes; i++) {
1433 // Ignore upper two bits
1435 result |= (chars[i] & 0x3F);
1438 if (result <= 0xFFFF) {
1439 return (char) result;
1441 // We need to handle this as a UTF16 surrogate (i.e. return
1444 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1445 return (char) ((result >> 10) | 0xD800);
1449 private struct UriScheme
1451 public string scheme;
1452 public string delimiter;
1453 public int defaultPort;
1455 public UriScheme (string s, string d, int p)
1463 static UriScheme [] schemes = new UriScheme [] {
1464 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1465 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1466 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1467 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1468 new UriScheme (UriSchemeMailto, ":", 25),
1469 new UriScheme (UriSchemeNews, ":", 119),
1470 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1471 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1474 internal static string GetSchemeDelimiter (string scheme)
1476 for (int i = 0; i < schemes.Length; i++)
1477 if (schemes [i].scheme == scheme)
1478 return schemes [i].delimiter;
1479 return Uri.SchemeDelimiter;
1482 internal static int GetDefaultPort (string scheme)
1485 UriParser parser = UriParser.GetParser (scheme);
1488 return parser.DefaultPort;
1490 for (int i = 0; i < schemes.Length; i++)
1491 if (schemes [i].scheme == scheme)
1492 return schemes [i].defaultPort;
1497 private string GetOpaqueWiseSchemeDelimiter ()
1502 return GetSchemeDelimiter (scheme);
1508 protected virtual bool IsBadFileSystemCharacter (char ch)
1510 // It does not always overlap with InvalidPathChars.
1511 int chInt = (int) ch;
1512 if (chInt < 32 || (chInt < 64 && chInt > 57))
1533 protected static bool IsExcludedCharacter (char ch)
1535 if (ch <= 32 || ch >= 127)
1538 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1539 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1540 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1546 private static bool IsPredefinedScheme (string scheme)
1570 protected virtual bool IsReservedCharacter (char ch)
1572 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1573 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1579 private UriParser parser;
1581 private UriParser Parser {
1584 parser = UriParser.GetParser (Scheme);
1587 set { parser = value; }
1590 public string GetComponents (UriComponents components, UriFormat format)
1592 return Parser.GetComponents (this, components, format);
1595 public bool IsBaseOf (Uri uri)
1597 return Parser.IsBaseOf (this, uri);
1600 public bool IsWellFormedOriginalString ()
1602 return Parser.IsWellFormedOriginalString (this);
1606 public Uri MakeRelativeUri (Uri uri)
1609 throw new ArgumentNullException ("uri");
1611 throw new NotImplementedException ();
1616 private const int MaxUriLength = 32766;
1618 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1620 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1621 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1622 throw new ArgumentException ("comparisonType", msg);
1625 if ((uri1 == null) && (uri2 == null))
1628 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1629 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1630 return String.Compare (s1, s2, comparisonType);
1634 public static string EscapeDataString (string stringToEscape)
1636 if (stringToEscape == null)
1637 throw new ArgumentNullException ("stringToEscape");
1639 if (stringToEscape.Length > MaxUriLength) {
1640 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1641 throw new UriFormatException (msg);
1644 throw new NotImplementedException ();
1648 public static string EscapeUriString (string stringToEscape)
1650 if (stringToEscape == null)
1651 throw new ArgumentNullException ("stringToEscape");
1653 if (stringToEscape.Length > MaxUriLength) {
1654 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1655 throw new UriFormatException (msg);
1658 throw new NotImplementedException ();
1661 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
1663 if (uriString == null)
1665 Uri uri = new Uri (uriString, uriKind);
1666 return uri.IsWellFormedOriginalString ();
1669 [MonoTODO ("rework code to avoid exception catching")]
1670 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
1673 result = new Uri (uriString, uriKind);
1676 catch (UriFormatException) {
1682 [MonoTODO ("rework code to avoid exception catching")]
1683 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
1686 // FIXME: this should call UriParser.Resolve
1687 result = new Uri (baseUri, relativeUri);
1690 catch (UriFormatException) {
1696 [MonoTODO ("rework code to avoid exception catching")]
1697 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
1700 // FIXME: this should call UriParser.Resolve
1701 result = new Uri (baseUri, relativeUri);
1704 catch (UriFormatException) {
1711 public static string UnescapeDataString (string stringToUnescape)
1713 if (stringToUnescape == null)
1714 throw new ArgumentNullException ("stringToUnescape");
1716 throw new NotImplementedException ();