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.
36 // See RFC 2396 for more info on URI's.
38 // TODO: optimize by parsing host string only once
40 using System.ComponentModel;
43 using System.Runtime.Serialization;
45 using System.Collections;
46 using System.Globalization;
49 // Disable warnings on Obsolete methods being used
51 #pragma warning disable 612
57 [TypeConverter (typeof (UriTypeConverter))]
58 public class Uri : ISerializable {
60 public class Uri : MarshalByRefObject, ISerializable {
63 // o scheme excludes the scheme delimiter
64 // o port is -1 to indicate no port is defined
65 // o path is empty or starts with / when scheme delimiter == "://"
66 // o query is empty or starts with ? char, escaped.
67 // o fragment is empty or starts with # char, unescaped.
68 // o all class variables are in escaped format when they are escapable,
69 // except cachedToString.
70 // o UNC is supported, as starts with "\\" for windows,
73 private bool isUnixFilePath;
74 private string source;
75 private string scheme = String.Empty;
76 private string host = String.Empty;
77 private int port = -1;
78 private string path = String.Empty;
79 private string query = String.Empty;
80 private string fragment = String.Empty;
81 private string userinfo = String.Empty;
83 private bool isOpaquePart;
84 private bool isAbsoluteUri = true;
86 private string [] segments;
88 private bool userEscaped;
89 private string cachedAbsoluteUri;
90 private string cachedToString;
91 private string cachedLocalPath;
92 private int cachedHashCode;
94 private static readonly string hexUpperChars = "0123456789ABCDEF";
98 public static readonly string SchemeDelimiter = "://";
99 public static readonly string UriSchemeFile = "file";
100 public static readonly string UriSchemeFtp = "ftp";
101 public static readonly string UriSchemeGopher = "gopher";
102 public static readonly string UriSchemeHttp = "http";
103 public static readonly string UriSchemeHttps = "https";
104 public static readonly string UriSchemeMailto = "mailto";
105 public static readonly string UriSchemeNews = "news";
106 public static readonly string UriSchemeNntp = "nntp";
108 public static readonly string UriSchemeNetPipe = "net.pipe";
109 public static readonly string UriSchemeNetTcp = "net.tcp";
114 public Uri (string uriString) : this (uriString, false)
118 protected Uri (SerializationInfo serializationInfo,
119 StreamingContext streamingContext) :
120 this (serializationInfo.GetString ("AbsoluteUri"), true)
125 public Uri (string uriString, UriKind uriKind)
131 case UriKind.Absolute:
133 throw new UriFormatException("Invalid URI: The format of the URI could not be "
136 case UriKind.Relative:
138 throw new UriFormatException("Invalid URI: The format of the URI could not be "
139 + "determined because the parameter 'uriString' represents an absolute URI.");
141 case UriKind.RelativeOrAbsolute:
144 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
145 throw new ArgumentException ("uriKind", msg);
149 public Uri (Uri baseUri, Uri relativeUri)
150 : this (baseUri, relativeUri.OriginalString, false)
152 // FIXME: this should call UriParser.Resolve
155 // note: doc says that dontEscape is always false but tests show otherwise
157 public Uri (string uriString, bool dontEscape)
159 userEscaped = dontEscape;
163 throw new UriFormatException("Invalid URI: The format of the URI could not be "
167 public Uri (string uriString, bool dontEscape)
169 userEscaped = dontEscape;
173 throw new UriFormatException("Invalid URI: The format of the URI could not be "
178 public Uri (Uri baseUri, string relativeUri)
179 : this (baseUri, relativeUri, false)
181 // FIXME: this should call UriParser.Resolve
185 [Obsolete ("dontEscape is always false")]
187 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
191 throw new ArgumentNullException ("baseUri");
192 if (relativeUri == null)
193 relativeUri = String.Empty;
196 throw new NullReferenceException ("baseUri");
198 // See RFC 2396 Par 5.2 and Appendix C
200 userEscaped = dontEscape;
202 // Check Windows UNC (for // it is scheme/host separator)
203 if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
204 source = relativeUri;
213 int pos = relativeUri.IndexOf (':');
216 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
218 // pos2 < 0 ... e.g. mailto
219 // pos2 > pos ... to block ':' in query part
220 if (pos2 > pos || pos2 < 0) {
221 // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
222 // 1) when the URI scheme in the
223 // relative path is different from that
224 // of the baseUri, or
225 // 2) the URI scheme is non-standard
226 // ones (non-standard URIs are always
227 // treated as absolute here), or
228 // 3) the relative URI path is absolute.
229 if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
230 !IsPredefinedScheme (baseUri.Scheme) ||
231 relativeUri.Length > pos + 1 &&
232 relativeUri [pos + 1] == '/') {
233 source = relativeUri;
242 relativeUri = relativeUri.Substring (pos + 1);
246 this.scheme = baseUri.scheme;
247 this.host = baseUri.host;
248 this.port = baseUri.port;
249 this.userinfo = baseUri.userinfo;
250 this.isUnc = baseUri.isUnc;
251 this.isUnixFilePath = baseUri.isUnixFilePath;
252 this.isOpaquePart = baseUri.isOpaquePart;
254 if (relativeUri == String.Empty) {
255 this.path = baseUri.path;
256 this.query = baseUri.query;
257 this.fragment = baseUri.fragment;
262 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
263 pos = relativeUri.IndexOf ('#');
266 fragment = relativeUri.Substring (pos);
268 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
269 relativeUri = relativeUri.Substring (0, pos);
273 pos = relativeUri.IndexOf ('?');
275 query = relativeUri.Substring (pos);
277 query = EscapeString (query);
278 relativeUri = relativeUri.Substring (0, pos);
281 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
282 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
283 source = scheme + ':' + relativeUri;
293 path = EscapeString (path);
300 if (relativeUri.Length > 0 || query.Length > 0) {
301 pos = path.LastIndexOf ('/');
303 path = path.Substring (0, pos + 1);
306 if(relativeUri.Length == 0)
315 pos = path.IndexOf ("./", startIndex);
319 path = path.Remove (0, 2);
320 else if (path [pos - 1] != '.')
321 path = path.Remove (pos, 2);
323 startIndex = pos + 1;
327 if (path.Length > 1 &&
328 path [path.Length - 1] == '.' &&
329 path [path.Length - 2] == '/')
330 path = path.Remove (path.Length - 1, 1);
335 pos = path.IndexOf ("/../", startIndex);
342 int pos2 = path.LastIndexOf ('/', pos - 1);
344 startIndex = pos + 1;
346 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
347 path = path.Remove (pos2 + 1, pos - pos2 + 3);
349 startIndex = pos + 1;
354 if (path.Length > 3 && path.EndsWith ("/..")) {
355 pos = path.LastIndexOf ('/', path.Length - 4);
357 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
358 path = path.Remove (pos + 1, path.Length - pos - 1);
362 path = EscapeString (path);
367 public string AbsolutePath {
373 // faster (mailto) and special (file) cases
376 if (path.Length == 0) {
377 string start = Scheme + SchemeDelimiter;
378 if (path.StartsWith (start))
391 public string AbsoluteUri {
393 EnsureAbsoluteUri ();
394 if (cachedAbsoluteUri == null) {
395 cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
396 if (query.Length > 0)
397 cachedAbsoluteUri += query;
398 if (fragment.Length > 0)
399 cachedAbsoluteUri += fragment;
401 return cachedAbsoluteUri;
405 public string Authority {
407 return (GetDefaultPort (Scheme) == port)
408 ? host : host + ":" + port;
412 public string Fragment {
414 EnsureAbsoluteUri ();
421 EnsureAbsoluteUri ();
426 public UriHostNameType HostNameType {
428 UriHostNameType ret = CheckHostName (Host);
429 if (ret != UriHostNameType.Unknown)
434 return UriHostNameType.Basic;
436 return (IsFile) ? UriHostNameType.Basic : ret;
439 // looks it always returns Basic...
440 return UriHostNameType.Basic; //.Unknown;
445 public bool IsDefaultPort {
446 get { return GetDefaultPort (Scheme) == port; }
450 get { return (Scheme == UriSchemeFile); }
453 public bool IsLoopback {
455 if (Host.Length == 0) {
463 if (host == "loopback" || host == "localhost")
467 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
469 } catch (FormatException) {}
472 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
473 } catch (FormatException) {}
480 // rule: This should be true only if
481 // - uri string starts from "\\", or
482 // - uri string starts from "//" (Samba way)
484 EnsureAbsoluteUri ();
489 public string LocalPath {
491 EnsureAbsoluteUri ();
492 if (cachedLocalPath != null)
493 return cachedLocalPath;
497 bool windows = (path.Length > 3 && path [1] == ':' &&
498 (path [2] == '\\' || path [2] == '/'));
501 string p = Unescape (path);
502 bool replace = windows;
504 replace |= (System.IO.Path.DirectorySeparatorChar == '\\');
507 cachedLocalPath = p.Replace ('/', '\\');
511 // support *nix and W32 styles
512 if (path.Length > 1 && path [1] == ':')
513 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
515 // LAMESPEC: ok, now we cannot determine
516 // if such URI like "file://foo/bar" is
517 // Windows UNC or unix file path, so
518 // they should be handled differently.
519 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
521 if (path.Length > 0) {
523 if ((path.Length > 1) || (path[0] != '/')) {
524 h += path.Replace ('/', '\\');
527 h += path.Replace ('/', '\\');
530 cachedLocalPath = "\\\\" + Unescape (h);
532 cachedLocalPath = Unescape (path);
534 if (cachedLocalPath.Length == 0)
535 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
536 return cachedLocalPath;
540 public string PathAndQuery {
541 get { return path + Query; }
546 EnsureAbsoluteUri ();
551 public string Query {
553 EnsureAbsoluteUri ();
558 public string Scheme {
560 EnsureAbsoluteUri ();
565 public string [] Segments {
567 EnsureAbsoluteUri ();
568 if (segments != null)
571 if (path.Length == 0) {
572 segments = new string [0];
576 string [] parts = path.Split ('/');
578 bool endSlash = path.EndsWith ("/");
579 if (parts.Length > 0 && endSlash) {
580 string [] newParts = new string [parts.Length - 1];
581 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
586 if (IsFile && path.Length > 1 && path [1] == ':') {
587 string [] newParts = new string [parts.Length + 1];
588 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
590 parts [0] = path.Substring (0, 2);
595 int end = parts.Length;
597 if (i != end - 1 || endSlash)
605 public bool UserEscaped {
606 get { return userEscaped; }
609 public string UserInfo {
611 EnsureAbsoluteUri ();
617 [MonoTODO ("add support for IPv6 address")]
618 public string DnsSafeHost {
619 get { return Unescape (Host); }
622 public bool IsAbsoluteUri {
623 get { return isAbsoluteUri; }
626 // LAMESPEC: source field is supplied in such case that this
627 // property makes sense. For such case that source field is
628 // not supplied (i.e. .ctor(Uri, string), this property
629 // makes no sense. To avoid silly regression it just returns
630 // ToString() value now. See bug #78374.
631 public string OriginalString {
632 get { return source != null ? source : ToString (); }
638 private void EnsureAbsoluteUri ()
642 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
646 public static UriHostNameType CheckHostName (string name)
648 if (name == null || name.Length == 0)
649 return UriHostNameType.Unknown;
651 if (IsIPv4Address (name))
652 return UriHostNameType.IPv4;
654 if (IsDomainAddress (name))
655 return UriHostNameType.Dns;
658 IPv6Address.Parse (name);
659 return UriHostNameType.IPv6;
660 } catch (FormatException) {}
662 return UriHostNameType.Unknown;
665 internal static bool IsIPv4Address (string name)
667 string [] captures = name.Split (new char [] {'.'});
668 if (captures.Length != 4)
670 for (int i = 0; i < 4; i++) {
672 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
673 if (d < 0 || d > 255)
675 } catch (Exception) {
682 internal static bool IsDomainAddress (string name)
684 int len = name.Length;
687 for (int i = 0; i < len; i++) {
690 if (!Char.IsLetterOrDigit (c))
692 } else if (c == '.') {
694 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
705 [Obsolete("This method does nothing, it has been obsoleted")]
707 protected virtual void Canonicalize ()
710 // This is flagged in the Microsoft documentation as used
711 // internally, no longer in use, and Obsolete.
715 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
716 public static bool CheckSchemeName (string schemeName)
718 if (schemeName == null || schemeName.Length == 0)
721 if (!IsAlpha (schemeName [0]))
724 int len = schemeName.Length;
725 for (int i = 1; i < len; i++) {
726 char c = schemeName [i];
727 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
734 private static bool IsAlpha (char c)
737 // as defined in rfc2234
738 // %x41-5A / %x61-7A (A-Z / a-z)
740 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
742 // Fx 1.x got this too large
743 return Char.IsLetter (c);
747 [MonoTODO ("Find out what this should do")]
751 protected virtual void CheckSecurity ()
755 public override bool Equals (object comparant)
757 if (comparant == null)
760 Uri uri = comparant as Uri;
761 if ((object) uri == null) {
762 string s = comparant as String;
768 return InternalEquals (uri);
771 // Assumes: uri != null
772 bool InternalEquals (Uri uri)
775 if (this.isAbsoluteUri != uri.isAbsoluteUri)
777 if (!this.isAbsoluteUri)
778 return this.source == uri.source;
781 CultureInfo inv = CultureInfo.InvariantCulture;
782 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
783 && this.host.ToLower (inv) == uri.host.ToLower (inv)
784 && this.port == uri.port
786 && this.query == uri.query
788 // Note: MS.NET 1.x has bug - ignores query check altogether
789 && this.query.ToLower (inv) == uri.query.ToLower (inv)
791 && this.path == uri.path;
795 public static bool operator == (Uri u1, Uri u2)
797 return object.Equals(u1, u2);
800 public static bool operator != (Uri u1, Uri u2)
806 public override int GetHashCode ()
808 if (cachedHashCode == 0) {
809 CultureInfo inv = CultureInfo.InvariantCulture;
811 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
812 ^ host.ToLower (inv).GetHashCode ()
815 ^ query.GetHashCode ()
817 ^ query.ToLower (inv).GetHashCode ()
819 ^ path.GetHashCode ();
822 cachedHashCode = source.GetHashCode ();
825 return cachedHashCode;
828 public string GetLeftPart (UriPartial part)
830 EnsureAbsoluteUri ();
833 case UriPartial.Scheme :
834 return scheme + GetOpaqueWiseSchemeDelimiter ();
835 case UriPartial.Authority :
836 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
839 StringBuilder s = new StringBuilder ();
841 s.Append (GetOpaqueWiseSchemeDelimiter ());
842 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
843 s.Append ('/'); // win32 file
844 if (userinfo.Length > 0)
845 s.Append (userinfo).Append ('@');
847 defaultPort = GetDefaultPort (scheme);
848 if ((port != -1) && (port != defaultPort))
849 s.Append (':').Append (port);
850 return s.ToString ();
851 case UriPartial.Path :
852 StringBuilder sb = new StringBuilder ();
854 sb.Append (GetOpaqueWiseSchemeDelimiter ());
855 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
856 sb.Append ('/'); // win32 file
857 if (userinfo.Length > 0)
858 sb.Append (userinfo).Append ('@');
860 defaultPort = GetDefaultPort (scheme);
861 if ((port != -1) && (port != defaultPort))
862 sb.Append (':').Append (port);
864 if (path.Length > 0) {
872 sb.Append (Reduce (path));
879 return sb.ToString ();
884 public static int FromHex (char digit)
886 if ('0' <= digit && digit <= '9') {
887 return (int) (digit - '0');
890 if ('a' <= digit && digit <= 'f')
891 return (int) (digit - 'a' + 10);
893 if ('A' <= digit && digit <= 'F')
894 return (int) (digit - 'A' + 10);
896 throw new ArgumentException ("digit");
899 public static string HexEscape (char character)
901 if (character > 255) {
902 throw new ArgumentOutOfRangeException ("character");
905 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
906 + hexUpperChars [((character & 0x0f))];
909 public static char HexUnescape (string pattern, ref int index)
912 throw new ArgumentException ("pattern");
914 if (index < 0 || index >= pattern.Length)
915 throw new ArgumentOutOfRangeException ("index");
917 if (!IsHexEncoding (pattern, index))
918 return pattern [index++];
921 int msb = FromHex (pattern [index++]);
922 int lsb = FromHex (pattern [index++]);
923 return (char) ((msb << 4) | lsb);
926 public static bool IsHexDigit (char digit)
928 return (('0' <= digit && digit <= '9') ||
929 ('a' <= digit && digit <= 'f') ||
930 ('A' <= digit && digit <= 'F'));
933 public static bool IsHexEncoding (string pattern, int index)
935 if ((index + 3) > pattern.Length)
938 return ((pattern [index++] == '%') &&
939 IsHexDigit (pattern [index++]) &&
940 IsHexDigit (pattern [index]));
945 // Implemented by copying most of the MakeRelative code
947 public Uri MakeRelativeUri (Uri uri)
950 throw new ArgumentNullException ("uri");
952 if (Host != uri.Host || Scheme != uri.Scheme)
955 if (this.path == uri.path)
956 return new Uri (String.Empty, UriKind.Relative);
958 string [] segments = this.Segments;
959 string [] segments2 = uri.Segments;
962 int max = Math.Min (segments.Length, segments2.Length);
964 if (segments [k] != segments2 [k])
967 string result = String.Empty;
968 for (int i = k + 1; i < segments.Length; i++)
970 for (int i = k; i < segments2.Length; i++)
971 result += segments2 [i];
973 return new Uri (result, UriKind.Relative);
976 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
978 public string MakeRelative (Uri toUri)
980 if ((this.Scheme != toUri.Scheme) ||
981 (this.Authority != toUri.Authority))
982 return toUri.ToString ();
984 if (this.path == toUri.path)
987 string [] segments = this.Segments;
988 string [] segments2 = toUri.Segments;
991 int max = Math.Min (segments.Length, segments2.Length);
993 if (segments [k] != segments2 [k])
996 string result = String.Empty;
997 for (int i = k + 1; i < segments.Length; i++)
999 for (int i = k; i < segments2.Length; i++)
1000 result += segments2 [i];
1005 public override string ToString ()
1007 if (cachedToString != null)
1008 return cachedToString;
1011 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1013 // Everything is contained in path in this case.
1014 cachedToString = Unescape (path);
1017 if (query.Length > 0) {
1018 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true) : Unescape (query, true);
1019 cachedToString += q;
1021 if (fragment.Length > 0)
1022 cachedToString += fragment;
1023 return cachedToString;
1027 protected void GetObjectData (SerializationInfo info, StreamingContext context)
1029 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1033 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1035 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1044 protected virtual void Escape ()
1046 path = EscapeString (path);
1052 protected static string EscapeString (string str)
1054 return EscapeString (str, false, true, true);
1057 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
1060 return String.Empty;
1062 byte [] data = Encoding.UTF8.GetBytes (str);
1063 StringBuilder s = new StringBuilder ();
1064 int len = data.Length;
1065 for (int i = 0; i < len; i++) {
1066 char c = (char) data [i];
1067 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1068 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1069 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1070 // space = <US-ASCII coded character 20 hexadecimal>
1071 // delims = "<" | ">" | "#" | "%" | <">
1072 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1074 // check for escape code already placed in str,
1075 // i.e. for encoding that follows the pattern
1076 // "%hexhex" in a string, where "hex" is a digit from 0-9
1077 // or a letter from A-F (case-insensitive).
1078 if('%' == c && IsHexEncoding(str,i))
1080 // if ,yes , copy it as is
1087 if ((c <= 0x20) || (c >= 0x7f) ||
1088 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
1089 (escapeHex && (c == '#')) ||
1090 (escapeBrackets && (c == '[' || c == ']')) ||
1091 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
1092 s.Append (HexEscape (c));
1100 return s.ToString ();
1103 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1104 // can avoid the "absolute uri" constraints of the .ctor() by
1105 // overriding with custom code.
1107 [Obsolete("The method has been deprecated. It is not used by the system.")]
1109 protected virtual void Parse ()
1116 private void ParseUri ()
1123 host = EscapeString (host, false, true, false);
1124 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1125 // host name present (but not an IPv6 address)
1126 host = host.ToLower (CultureInfo.InvariantCulture);
1129 if (path.Length > 0) {
1130 path = EscapeString (path);
1137 protected virtual string Unescape (string str)
1139 return Unescape (str, false);
1142 internal static string Unescape (string str, bool excludeSpecial)
1145 return String.Empty;
1146 StringBuilder s = new StringBuilder ();
1147 int len = str.Length;
1148 for (int i = 0; i < len; i++) {
1152 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1153 if (excludeSpecial && x == '#')
1155 else if (excludeSpecial && x == '%')
1157 else if (excludeSpecial && x == '?')
1161 if (surrogate != char.MinValue)
1162 s.Append (surrogate);
1168 return s.ToString ();
1174 private void ParseAsWindowsUNC (string uriString)
1176 scheme = UriSchemeFile;
1178 fragment = String.Empty;
1179 query = String.Empty;
1182 uriString = uriString.TrimStart (new char [] {'\\'});
1183 int pos = uriString.IndexOf ('\\');
1185 path = uriString.Substring (pos);
1186 host = uriString.Substring (0, pos);
1187 } else { // "\\\\server"
1189 path = String.Empty;
1191 path = path.Replace ("\\", "/");
1194 private void ParseAsWindowsAbsoluteFilePath (string uriString)
1196 if (uriString.Length > 2 && uriString [2] != '\\'
1197 && uriString [2] != '/')
1198 throw new UriFormatException ("Relative file path is not allowed.");
1199 scheme = UriSchemeFile;
1200 host = String.Empty;
1202 path = uriString.Replace ("\\", "/");
1203 fragment = String.Empty;
1204 query = String.Empty;
1207 private void ParseAsUnixAbsoluteFilePath (string uriString)
1209 isUnixFilePath = true;
1210 scheme = UriSchemeFile;
1212 fragment = String.Empty;
1213 query = String.Empty;
1214 host = String.Empty;
1217 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1218 uriString = uriString.TrimStart (new char [] {'/'});
1219 // Now we don't regard //foo/bar as "foo" host.
1221 int pos = uriString.IndexOf ('/');
1223 path = '/' + uriString.Substring (pos + 1);
1224 host = uriString.Substring (0, pos);
1225 } else { // "///server"
1227 path = String.Empty;
1230 path = '/' + uriString;
1236 // this parse method is as relaxed as possible about the format
1237 // it will hardly ever throw a UriFormatException
1238 private void Parse (string uriString)
1243 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1247 if (uriString == null)
1248 throw new ArgumentNullException ("uriString");
1250 int len = uriString.Length;
1252 throw new UriFormatException ();
1257 // Identify Windows path, unix path, or standard URI.
1258 pos = uriString.IndexOf (':');
1260 throw new UriFormatException("Invalid URI: "
1261 + "The format of the URI could not be "
1263 } else if (pos < 0) {
1264 // It must be Unix file path or Windows UNC
1265 if (uriString [0] == '/')
1266 ParseAsUnixAbsoluteFilePath (uriString);
1267 else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\')
1268 ParseAsWindowsUNC (uriString);
1271 isAbsoluteUri = false;
1276 else if (pos == 1) {
1277 if (!IsAlpha (uriString [0]))
1278 throw new UriFormatException ("URI scheme must start with a letter.");
1279 // This means 'a:' == windows full path.
1280 ParseAsWindowsAbsoluteFilePath (uriString);
1285 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1286 // Check scheme name characters as specified in RFC2396.
1287 // Note: different checks in 1.x and 2.0
1288 if (!CheckSchemeName (scheme)) {
1289 string msg = Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
1290 throw new UriFormatException (msg);
1293 // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1294 int startpos = pos + 1;
1295 int endpos = uriString.Length;
1298 pos = uriString.IndexOf ('#', startpos);
1299 if (!IsUnc && pos != -1) {
1301 fragment = uriString.Substring (pos);
1303 fragment = "#" + EscapeString (uriString.Substring (pos+1));
1309 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1311 query = uriString.Substring (pos, endpos-pos);
1314 query = EscapeString (query);
1318 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
1319 (endpos-startpos < 2) ||
1320 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))
1321 throw new UriFormatException ("Invalid URI: The Authority/Host could not be parsed.");
1324 bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1325 bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1326 if (startsWithSlashSlash) {
1327 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1330 if (scheme == UriSchemeFile) {
1331 int num_leading_slash = 2;
1332 for (int i = startpos; i < endpos; i++) {
1333 if (uriString [i] != '/')
1335 num_leading_slash++;
1337 if (num_leading_slash >= 4) {
1338 unixAbsPath = false;
1339 while (startpos < endpos && uriString[startpos] == '/') {
1342 } else if (num_leading_slash >= 3) {
1347 if (endpos - startpos > 1 && uriString [startpos + 1] == ':')
1348 unixAbsPath = false;
1350 } else if (!IsPredefinedScheme (scheme)) {
1351 path = uriString.Substring(startpos, endpos-startpos);
1352 isOpaquePart = true;
1357 pos = uriString.IndexOf ('/', startpos, endpos-startpos);
1361 if ((scheme != Uri.UriSchemeMailto) &&
1363 (scheme != Uri.UriSchemeFile) &&
1365 (scheme != Uri.UriSchemeNews))
1368 path = uriString.Substring (pos, endpos-pos);
1373 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1375 userinfo = uriString.Substring (startpos, pos-startpos);
1381 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1384 if (pos != -1 && pos != endpos - 1) {
1385 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1386 if (portStr.Length > 1 && portStr[portStr.Length - 1] != ']') {
1389 port = (int) UInt16.Parse (portStr, CultureInfo.InvariantCulture);
1391 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1394 } catch (Exception) {
1395 throw new UriFormatException ("Invalid URI: Invalid port number");
1399 port = GetDefaultPort (scheme);
1404 port = GetDefaultPort (scheme);
1409 uriString = uriString.Substring(startpos, endpos-startpos);
1413 path = '/' + uriString;
1414 host = String.Empty;
1415 } else if (host.Length == 2 && host [1] == ':') {
1418 host = String.Empty;
1419 } else if (isUnixFilePath) {
1420 uriString = "//" + uriString;
1421 host = String.Empty;
1422 } else if (scheme == UriSchemeFile) {
1424 } else if (scheme == UriSchemeNews) {
1425 // no host for 'news', misinterpreted path
1426 if (host.Length > 0) {
1428 host = String.Empty;
1430 } else if (host.Length == 0 &&
1431 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp
1432 || scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1433 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1436 bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
1437 if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
1439 host = "[" + IPv6Address.Parse (host).ToString (true) + "]";
1445 if (badhost && (scheme != "monodoc")) {
1446 string msg = Locale.GetText ("Invalid URI: The hostname could not be parsed.");
1447 throw new UriFormatException (msg);
1450 if ((scheme != Uri.UriSchemeMailto) &&
1451 (scheme != Uri.UriSchemeNews) &&
1452 (scheme != Uri.UriSchemeFile)) {
1453 path = Reduce (path);
1457 private static string Reduce (string path)
1459 path = path.Replace ('\\','/');
1460 ArrayList result = new ArrayList ();
1462 for (int startpos = 0; startpos < path.Length; ) {
1463 int endpos = path.IndexOf('/', startpos);
1464 if (endpos == -1) endpos = path.Length;
1465 string current = path.Substring (startpos, endpos-startpos);
1466 startpos = endpos + 1;
1467 if (current == "" || current == "." )
1470 if (current == "..") {
1471 int resultCount = result.Count;
1473 // in 2.0 profile, skip leading ".." parts
1474 if (resultCount == 0) {
1478 result.RemoveAt (resultCount - 1);
1481 // in 1.x profile, retain leading ".." parts, and only reduce
1482 // URI is previous part is not ".."
1483 if (resultCount > 0) {
1484 if ((string) result[resultCount - 1] != "..") {
1485 result.RemoveAt (resultCount - 1);
1492 result.Add (current);
1495 if (result.Count == 0)
1498 StringBuilder res = new StringBuilder();
1499 if (path [0] == '/')
1503 foreach (string part in result) {
1512 if (path.EndsWith ("/"))
1515 return res.ToString();
1518 // A variant of HexUnescape() which can decode multi-byte escaped
1519 // sequences such as (e.g.) %E3%81%8B into a single character
1520 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1522 surrogate = char.MinValue;
1524 if (pattern == null)
1525 throw new ArgumentException ("pattern");
1527 if (index < 0 || index >= pattern.Length)
1528 throw new ArgumentOutOfRangeException ("index");
1530 if (!IsHexEncoding (pattern, index))
1531 return pattern [index++];
1533 int orig_index = index++;
1534 int msb = FromHex (pattern [index++]);
1535 int lsb = FromHex (pattern [index++]);
1537 // We might be dealing with a multi-byte character:
1538 // The number of ones at the top-end of the first byte will tell us
1539 // how many bytes will make up this character.
1542 while ((msb_copy & 0x8) == 0x8) {
1547 // We might be dealing with a single-byte character:
1548 // If there was only 0 or 1 leading ones then we're not dealing
1549 // with a multi-byte character.
1551 return (char) ((msb << 4) | lsb);
1553 // Now that we know how many bytes *should* follow, we'll check them
1554 // to ensure we are dealing with a valid multi-byte character.
1555 byte [] chars = new byte [num_bytes];
1556 bool all_invalid = false;
1557 chars[0] = (byte) ((msb << 4) | lsb);
1559 for (int i = 1; i < num_bytes; i++) {
1560 if (!IsHexEncoding (pattern, index++)) {
1565 // All following bytes must be in the form 10xxxxxx
1566 int cur_msb = FromHex (pattern [index++]);
1567 if ((cur_msb & 0xc) != 0x8) {
1572 int cur_lsb = FromHex (pattern [index++]);
1573 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1576 // If what looked like a multi-byte character is invalid, then we'll
1577 // just return the first byte as a single byte character.
1579 index = orig_index + 3;
1580 return (char) chars[0];
1583 // Otherwise, we're dealing with a valid multi-byte character.
1584 // We need to ignore the leading ones from the first byte:
1585 byte mask = (byte) 0xFF;
1586 mask >>= (num_bytes + 1);
1587 int result = chars[0] & mask;
1589 // The result will now be built up from the following bytes.
1590 for (int i = 1; i < num_bytes; i++) {
1591 // Ignore upper two bits
1593 result |= (chars[i] & 0x3F);
1596 if (result <= 0xFFFF) {
1597 return (char) result;
1599 // We need to handle this as a UTF16 surrogate (i.e. return
1602 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1603 return (char) ((result >> 10) | 0xD800);
1607 private struct UriScheme
1609 public string scheme;
1610 public string delimiter;
1611 public int defaultPort;
1613 public UriScheme (string s, string d, int p)
1621 static UriScheme [] schemes = new UriScheme [] {
1622 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1623 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1624 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1625 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1626 new UriScheme (UriSchemeMailto, ":", 25),
1627 new UriScheme (UriSchemeNews, ":", 119),
1628 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1629 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1632 internal static string GetSchemeDelimiter (string scheme)
1634 for (int i = 0; i < schemes.Length; i++)
1635 if (schemes [i].scheme == scheme)
1636 return schemes [i].delimiter;
1637 return Uri.SchemeDelimiter;
1640 internal static int GetDefaultPort (string scheme)
1643 UriParser parser = UriParser.GetParser (scheme);
1646 return parser.DefaultPort;
1648 for (int i = 0; i < schemes.Length; i++)
1649 if (schemes [i].scheme == scheme)
1650 return schemes [i].defaultPort;
1655 private string GetOpaqueWiseSchemeDelimiter ()
1660 return GetSchemeDelimiter (scheme);
1666 protected virtual bool IsBadFileSystemCharacter (char ch)
1668 // It does not always overlap with InvalidPathChars.
1669 int chInt = (int) ch;
1670 if (chInt < 32 || (chInt < 64 && chInt > 57))
1691 protected static bool IsExcludedCharacter (char ch)
1693 if (ch <= 32 || ch >= 127)
1696 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1697 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1698 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1704 private static bool IsPredefinedScheme (string scheme)
1728 protected virtual bool IsReservedCharacter (char ch)
1730 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1731 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1737 private UriParser parser;
1739 private UriParser Parser {
1742 parser = UriParser.GetParser (Scheme);
1745 set { parser = value; }
1748 public string GetComponents (UriComponents components, UriFormat format)
1750 return Parser.GetComponents (this, components, format);
1753 public bool IsBaseOf (Uri uri)
1755 return Parser.IsBaseOf (this, uri);
1758 [MonoNotSupported ("IsWellFormedOriginalString is not supported")]
1759 public bool IsWellFormedOriginalString ()
1761 return Parser.IsWellFormedOriginalString (this);
1766 private const int MaxUriLength = 32766;
1768 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1770 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1771 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1772 throw new ArgumentException ("comparisonType", msg);
1775 if ((uri1 == null) && (uri2 == null))
1778 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1779 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1780 return String.Compare (s1, s2, comparisonType);
1784 // The rules for EscapeDataString
1786 static bool NeedToEscapeDataChar (char b)
1788 return !((b >= 'A' && b <= 'Z') ||
1789 (b >= 'a' && b <= 'z') ||
1790 (b >= '0' && b <= '9') ||
1791 b == '_' || b == '~' || b == '!' || b == '\'' ||
1792 b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
1795 public static string EscapeDataString (string stringToEscape)
1797 if (stringToEscape == null)
1798 throw new ArgumentNullException ("stringToEscape");
1800 if (stringToEscape.Length > MaxUriLength) {
1801 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1802 throw new UriFormatException (msg);
1804 bool escape = false;
1805 foreach (char c in stringToEscape){
1806 if (NeedToEscapeDataChar (c)){
1812 return stringToEscape;
1815 StringBuilder sb = new StringBuilder ();
1816 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1817 foreach (byte b in bytes){
1818 if (NeedToEscapeDataChar ((char) b))
1819 sb.Append (HexEscape ((char) b));
1821 sb.Append ((char) b);
1823 return sb.ToString ();
1827 // The rules for EscapeUriString
1829 static bool NeedToEscapeUriChar (char b)
1831 return !((b >= 'A' && b <= 'Z') ||
1832 (b >= 'a' && b <= 'z') ||
1833 (b >= '&' && b <= ';') ||
1834 b == '!' || b == '#' || b == '$' || b == '=' ||
1835 b == '?' || b == '@' || b == '_' || b == '~');
1838 public static string EscapeUriString (string stringToEscape)
1840 if (stringToEscape == null)
1841 throw new ArgumentNullException ("stringToEscape");
1843 if (stringToEscape.Length > MaxUriLength) {
1844 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1845 throw new UriFormatException (msg);
1848 bool escape = false;
1849 foreach (char c in stringToEscape){
1850 if (NeedToEscapeUriChar (c)){
1856 return stringToEscape;
1858 StringBuilder sb = new StringBuilder ();
1859 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1860 foreach (byte b in bytes){
1861 if (NeedToEscapeUriChar ((char) b))
1862 sb.Append (HexEscape ((char) b));
1864 sb.Append ((char) b);
1866 return sb.ToString ();
1869 [MonoNotSupported ("IsWellFormedUriString is not supported")]
1870 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
1872 if (uriString == null)
1874 Uri uri = new Uri (uriString, uriKind);
1875 return uri.IsWellFormedOriginalString ();
1878 // [MonoTODO ("rework code to avoid exception catching")]
1879 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
1882 result = new Uri (uriString, uriKind);
1885 catch (UriFormatException) {
1891 // [MonoTODO ("rework code to avoid exception catching")]
1892 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
1895 // FIXME: this should call UriParser.Resolve
1896 result = new Uri (baseUri, relativeUri);
1899 catch (UriFormatException) {
1905 //[MonoTODO ("rework code to avoid exception catching")]
1906 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
1909 // FIXME: this should call UriParser.Resolve
1910 result = new Uri (baseUri, relativeUri);
1913 catch (UriFormatException) {
1919 public static string UnescapeDataString (string stringToUnescape)
1921 if (stringToUnescape == null)
1922 throw new ArgumentNullException ("stringToUnescape");
1924 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
1925 return stringToUnescape;
1927 StringBuilder output = new StringBuilder ();
1928 long len = stringToUnescape.Length;
1929 MemoryStream bytes = new MemoryStream ();
1932 for (int i = 0; i < len; i++) {
1933 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
1934 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
1935 if (bytes.Length > 0) {
1936 output.Append (GetChars (bytes, Encoding.UTF8));
1937 bytes.SetLength (0);
1940 xchar = GetChar (stringToUnescape, i + 2, 4);
1942 output.Append ((char) xchar);
1946 output.Append ('%');
1949 else if ((xchar = GetChar (stringToUnescape, i + 1, 2)) != -1) {
1950 bytes.WriteByte ((byte) xchar);
1954 output.Append ('%');
1959 if (bytes.Length > 0) {
1960 output.Append (GetChars (bytes, Encoding.UTF8));
1961 bytes.SetLength (0);
1964 output.Append (stringToUnescape [i]);
1967 if (bytes.Length > 0) {
1968 output.Append (GetChars (bytes, Encoding.UTF8));
1972 return output.ToString ();
1975 private static int GetInt (byte b)
1978 if (c >= '0' && c <= '9')
1981 if (c >= 'a' && c <= 'f')
1982 return c - 'a' + 10;
1984 if (c >= 'A' && c <= 'F')
1985 return c - 'A' + 10;
1990 private static int GetChar (string str, int offset, int length)
1993 int end = length + offset;
1994 for (int i = offset; i < end; i++) {
1999 int current = GetInt ((byte) c);
2002 val = (val << 4) + current;
2008 private static char [] GetChars (MemoryStream b, Encoding e)
2010 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);