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>
11 // Stephane Delcroix <stephane@delcroix.org>
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com)
17 // Copyright (c) 2009 Stephane Delcroix
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 // See RFC 2396 for more info on URI's.
40 // TODO: optimize by parsing host string only once
42 using System.ComponentModel;
45 using System.Runtime.Serialization;
47 using System.Collections;
48 using System.Collections.Generic;
49 using System.Globalization;
52 // Disable warnings on Obsolete methods being used
54 #pragma warning disable 612
59 [TypeConverter (typeof (UriTypeConverter))]
60 public class Uri : ISerializable {
62 // o scheme excludes the scheme delimiter
63 // o port is -1 to indicate no port is defined
64 // o path is empty or starts with / when scheme delimiter == "://"
65 // o query is empty or starts with ? char, escaped.
66 // o fragment is empty or starts with # char, unescaped.
67 // o all class variables are in escaped format when they are escapable,
68 // except cachedToString.
69 // o UNC is supported, as starts with "\\" for windows,
72 private bool isUnixFilePath;
73 private string source;
74 private string scheme = String.Empty;
75 private string host = String.Empty;
76 private int port = -1;
77 private string path = String.Empty;
78 private string query = String.Empty;
79 private string fragment = String.Empty;
80 private string userinfo;
82 private bool isOpaquePart;
83 private bool isAbsoluteUri = true;
84 private long scope_id;
86 private List<string> segments;
88 private bool userEscaped;
89 private string cachedAbsoluteUri;
90 private string cachedToString;
91 private string cachedLocalPath;
92 private int cachedHashCode;
95 private static volatile bool s_IriParsing = true;
97 private static volatile bool s_IriParsing = false;
100 public static bool IriParsing {
101 get { return s_IriParsing; }
102 set { s_IriParsing = value; }
106 private static readonly string hexUpperChars = "0123456789ABCDEF";
107 private static readonly string [] Empty = new string [0];
108 private static bool isWin32 = (Path.DirectorySeparatorChar == '\\');
110 static readonly char[] hexUpperChars = new [] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
115 public static readonly string SchemeDelimiter = "://";
116 public static readonly string UriSchemeFile = "file";
117 public static readonly string UriSchemeFtp = "ftp";
118 public static readonly string UriSchemeGopher = "gopher";
119 public static readonly string UriSchemeHttp = "http";
120 public static readonly string UriSchemeHttps = "https";
121 public static readonly string UriSchemeMailto = "mailto";
122 public static readonly string UriSchemeNews = "news";
123 public static readonly string UriSchemeNntp = "nntp";
124 public static readonly string UriSchemeNetPipe = "net.pipe";
125 public static readonly string UriSchemeNetTcp = "net.tcp";
127 internal static readonly string UriSchemeTelnet = "telnet";
128 internal static readonly string UriSchemeLdap = "ldap";
129 internal static readonly string UriSchemeUuid = "uuid";
131 private static readonly string [] knownUriSchemes =
149 var iriparsingVar = Environment.GetEnvironmentVariable ("MONO_URI_IRIPARSING");
150 if (iriparsingVar == "true")
152 if (iriparsingVar == "false")
156 public Uri (string uriString) : this (uriString, false)
160 protected Uri (SerializationInfo serializationInfo, StreamingContext streamingContext)
162 string uri = serializationInfo.GetString ("AbsoluteUri");
163 if (uri.Length > 0) {
165 ParseUri(UriKind.Absolute);
167 uri = serializationInfo.GetString ("RelativeUri");
168 if (uri.Length > 0) {
170 ParseUri(UriKind.Relative);
172 throw new ArgumentException("Uri string was null or empty.");
177 public Uri (string uriString, UriKind uriKind)
183 case UriKind.Absolute:
185 throw new UriFormatException("Invalid URI: The format of the URI could not be "
188 case UriKind.Relative:
190 throw new UriFormatException("Invalid URI: The format of the URI could not be "
191 + "determined because the parameter 'uriString' represents an absolute URI.");
193 case UriKind.RelativeOrAbsolute:
196 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
197 throw new ArgumentException (msg);
202 // An exception-less constructor, returns success
203 // condition on the out parameter `success'.
205 Uri (string uriString, UriKind uriKind, out bool success)
207 if (uriString == null) {
212 if (uriKind != UriKind.RelativeOrAbsolute &&
213 uriKind != UriKind.Absolute &&
214 uriKind != UriKind.Relative) {
215 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
216 throw new ArgumentException (msg);
220 if (ParseNoExceptions (uriKind, uriString) != null)
226 case UriKind.Absolute:
230 case UriKind.Relative:
234 case UriKind.RelativeOrAbsolute:
243 public Uri (Uri baseUri, Uri relativeUri)
245 Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
246 // FIXME: this should call UriParser.Resolve
249 // note: doc says that dontEscape is always false but tests show otherwise
251 public Uri (string uriString, bool dontEscape)
253 userEscaped = dontEscape;
255 ParseUri (UriKind.Absolute);
257 throw new UriFormatException("Invalid URI: The format of the URI could not be "
258 + "determined: " + uriString);
261 public Uri (Uri baseUri, string relativeUri)
263 Merge (baseUri, relativeUri);
264 // FIXME: this should call UriParser.Resolve
267 [Obsolete ("dontEscape is always false")]
268 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
270 userEscaped = dontEscape;
271 Merge (baseUri, relativeUri);
274 private void Merge (Uri baseUri, string relativeUri)
277 throw new ArgumentNullException ("baseUri");
278 if (!baseUri.IsAbsoluteUri)
279 throw new ArgumentOutOfRangeException ("baseUri");
280 if (relativeUri == null)
281 relativeUri = String.Empty;
283 // See RFC 2396 Par 5.2 and Appendix C
285 // Check Windows UNC (for // it is scheme/host separator)
286 if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
287 source = relativeUri;
288 ParseUri (UriKind.Absolute);
292 int pos = relativeUri.IndexOf (':');
295 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
297 // pos2 < 0 ... e.g. mailto
298 // pos2 > pos ... to block ':' in query part
299 if (pos2 > pos || pos2 < 0) {
300 // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
301 // 1) when the URI scheme in the
302 // relative path is different from that
303 // of the baseUri, or
304 // 2) the URI scheme is non-standard
305 // ones (non-standard URIs are always
306 // treated as absolute here), or
307 // 3) the relative URI path is absolute.
308 if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
309 !IsPredefinedScheme (baseUri.Scheme) ||
310 (relativeUri.Length > pos + 1 && relativeUri [pos + 1] == '/')) {
312 if (Uri.TryCreate (relativeUri, UriKind.Absolute, out tmp)) {
313 source = relativeUri;
314 ParseUri (UriKind.Absolute);
316 } else if (pos == 1) {
317 // special case as this looks like a windows path
318 string msg = ParseAsWindowsAbsoluteFilePath (relativeUri);
320 throw new UriFormatException (msg);
322 // otherwise continue with 'full' relativeUri
325 relativeUri = relativeUri.Substring (pos + 1);
329 this.scheme = baseUri.scheme;
330 this.host = baseUri.host;
331 this.port = baseUri.port;
332 this.userinfo = baseUri.userinfo;
333 this.isUnc = baseUri.isUnc;
334 this.isUnixFilePath = baseUri.isUnixFilePath;
335 this.isOpaquePart = baseUri.isOpaquePart;
337 if (relativeUri.Length == 0) {
338 this.path = baseUri.path;
339 this.query = baseUri.query;
340 this.fragment = baseUri.fragment;
341 this.source = baseUri.OriginalString;
345 var formatFlags = UriHelper.FormatFlags.None;
346 if (UriHelper.HasCharactersToNormalize (relativeUri))
347 formatFlags |= UriHelper.FormatFlags.HasUriCharactersToNormalize;
350 formatFlags |= UriHelper.FormatFlags.UserEscaped;
353 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
354 string original_fragment = String.Empty;
355 pos = relativeUri.IndexOf ('#');
357 original_fragment = relativeUri.Substring (pos);
358 fragment = "#" + UriHelper.FormatAbsolute(relativeUri.Substring (pos+1), scheme,
359 UriComponents.Fragment, UriFormat.UriEscaped, formatFlags);
361 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
364 bool consider_query = false;
367 pos = relativeUri.IndexOf ('?');
369 query = relativeUri.Substring (pos);
370 query = UriHelper.FormatAbsolute(query, scheme,
371 UriComponents.Query, UriFormat.UriEscaped, formatFlags);
372 #if !NET_4_0 && !MOBILE
373 consider_query = query.Length > 0;
375 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
376 } else if (relativeUri.Length == 0) {
377 // if there is no relative path then we keep the Query and Fragment from the absolute
378 query = baseUri.query;
381 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
382 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
383 source = scheme + ':' + relativeUri + query + original_fragment;
384 ParseUri (UriKind.Absolute);
388 source = GetLeftPart (UriPartial.Authority) + path + query + original_fragment;
389 path = UriHelper.FormatAbsolute (path, scheme,
390 UriComponents.Path, UriFormat.UriEscaped, formatFlags);;
397 if ((relativeUri.Length > 0) || consider_query) {
398 pos = path.LastIndexOf ('/');
400 path = path.Substring (0, pos + 1);
403 if (relativeUri.Length == 0) {
404 // when merging URI the OriginalString is not quite original
405 source = GetLeftPart (UriPartial.Authority) + path + query + original_fragment;
415 pos = path.IndexOf ("./", startIndex);
419 path = path.Remove (0, 2);
420 else if (path [pos - 1] != '.')
421 path = path.Remove (pos, 2);
423 startIndex = pos + 1;
427 if (path.Length > 1 &&
428 path [path.Length - 1] == '.' &&
429 path [path.Length - 2] == '/')
430 path = path.Remove (path.Length - 1, 1);
435 pos = path.IndexOf ("/../", startIndex);
442 int pos2 = path.LastIndexOf ('/', pos - 1);
444 startIndex = pos + 1;
446 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
447 path = path.Remove (pos2 + 1, pos - pos2 + 3);
449 startIndex = pos + 1;
454 if (path.Length > 3 && path.EndsWith ("/..")) {
455 pos = path.LastIndexOf ('/', path.Length - 4);
457 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
458 path = path.Remove (pos + 1, path.Length - pos - 1);
462 while (path.StartsWith ("/../", StringComparison.Ordinal))
463 path = path.Substring (3);
465 // when merging URI the OriginalString is not quite original
466 source = GetLeftPart (UriPartial.Authority) + path + query + original_fragment;
468 path = UriHelper.FormatAbsolute (path, scheme,
469 UriComponents.Path, UriFormat.UriEscaped, formatFlags);
474 public string AbsolutePath {
476 EnsureAbsoluteUri ();
477 if (scheme == "mailto" || scheme == "file")
478 // faster (mailto) and special (file) cases
481 if (path.Length == 0) {
482 string start = scheme + SchemeDelimiter;
483 if (path.StartsWith (start, StringComparison.Ordinal))
492 public string AbsoluteUri {
494 EnsureAbsoluteUri ();
496 if (cachedAbsoluteUri == null)
497 cachedAbsoluteUri = GetComponents (UriComponents.AbsoluteUri, UriFormat.UriEscaped);
499 return cachedAbsoluteUri;
503 public string Authority {
505 EnsureAbsoluteUri ();
506 return (GetDefaultPort (Scheme) == port)
507 ? host : host + ":" + port;
511 public string Fragment {
513 EnsureAbsoluteUri ();
520 EnsureAbsoluteUri ();
525 public UriHostNameType HostNameType {
527 EnsureAbsoluteUri ();
528 UriHostNameType ret = CheckHostName (Host);
529 if (ret != UriHostNameType.Unknown)
532 if (scheme == "mailto")
533 return UriHostNameType.Basic;
534 return (IsFile) ? UriHostNameType.Basic : ret;
538 public bool IsDefaultPort {
540 EnsureAbsoluteUri ();
541 return GetDefaultPort (Scheme) == port;
547 EnsureAbsoluteUri ();
548 return (Scheme == UriSchemeFile);
552 public bool IsLoopback {
554 EnsureAbsoluteUri ();
556 if (Host.Length == 0) {
560 if (host == "loopback" || host == "localhost")
564 if (IPAddress.TryParse (host, out result))
565 if (IPAddress.Loopback.Equals (result))
569 if (IPv6Address.TryParse (host, out result6)){
570 if (IPv6Address.IsLoopback (result6))
579 // rule: This should be true only if
580 // - uri string starts from "\\", or
581 // - uri string starts from "//" (Samba way)
583 EnsureAbsoluteUri ();
588 private bool IsLocalIdenticalToAbsolutePath ()
593 if ((scheme == Uri.UriSchemeNews) || (scheme == Uri.UriSchemeNntp) || (scheme == Uri.UriSchemeFtp))
596 return IsWellFormedOriginalString ();
599 public string LocalPath {
601 EnsureAbsoluteUri ();
602 if (cachedLocalPath != null)
603 return cachedLocalPath;
605 if (IsLocalIdenticalToAbsolutePath ()) {
606 cachedLocalPath = Unescape (AbsolutePath);
607 return cachedLocalPath;
611 string p = Unescape (path);
612 bool windows = (path.Length > 3 && path [1] == ':' &&
613 (path [2] == '\\' || path [2] == '/'));
616 cachedLocalPath = p.Replace ('/', '\\');
620 // support *nix and W32 styles
621 if (path.Length > 1 && path [1] == ':')
622 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
624 // LAMESPEC: ok, now we cannot determine
625 // if such URI like "file://foo/bar" is
626 // Windows UNC or unix file path, so
627 // they should be handled differently.
628 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
630 if (path.Length > 0) {
631 if ((path.Length > 1) || (path[0] != '/')) {
632 h += path.Replace ('/', '\\');
635 cachedLocalPath = "\\\\" + Unescape (h);
637 cachedLocalPath = Unescape (path);
639 if (cachedLocalPath.Length == 0)
640 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
641 return cachedLocalPath;
645 public string PathAndQuery {
647 EnsureAbsoluteUri ();
654 EnsureAbsoluteUri ();
659 public string Query {
661 EnsureAbsoluteUri ();
666 public string Scheme {
668 EnsureAbsoluteUri ();
673 public string [] Segments {
675 EnsureAbsoluteUri ();
677 // return a (pre-allocated) empty array
678 if (path.Length == 0)
682 return EmptyArray<string>.Value;
684 // do not return the original array (since items can be changed)
685 if (segments != null)
686 return segments.ToArray ();
688 List<string> list = new List<string> ();
689 StringBuilder current = new StringBuilder ();
690 for (int i = 0; i < path.Length; i++) {
694 current.Append (path [i]);
695 list.Add (current.ToString ());
699 if ((i < path.Length - 2) && (path [i + 1] == '5' && path [i + 2] == 'C')) {
700 current.Append ("%5C");
701 list.Add (current.ToString ());
705 current.Append ('%');
709 current.Append (path [i]);
714 if (current.Length > 0)
715 list.Add (current.ToString ());
717 if (IsFile && (list.Count > 0)) {
718 string first = list [0];
719 if ((first.Length > 1) && (first [1] == ':')) {
720 list.Insert (0, "/");
724 return segments.ToArray ();
728 public bool UserEscaped {
729 get { return userEscaped; }
732 public string UserInfo {
734 EnsureAbsoluteUri ();
735 return userinfo == null ? String.Empty : userinfo;
739 public string DnsSafeHost {
741 EnsureAbsoluteUri ();
743 if (HostNameType == UriHostNameType.IPv6) {
744 host = Host.Substring (1, Host.Length - 2);
746 host += "%" + scope_id.ToString ();
748 return Unescape (host);
752 public bool IsAbsoluteUri {
753 get { return isAbsoluteUri; }
756 public string OriginalString {
757 get { return source; }
762 public static UriHostNameType CheckHostName (string name)
764 if (name == null || name.Length == 0)
765 return UriHostNameType.Unknown;
767 if (IsIPv4Address (name))
768 return UriHostNameType.IPv4;
770 if (IsDomainAddress (name))
771 return UriHostNameType.Dns;
774 if (IPv6Address.TryParse (name, out addr))
775 return UriHostNameType.IPv6;
777 return UriHostNameType.Unknown;
780 internal static bool IsIPv4Address (string name)
782 string [] captures = name.Split (new char [] {'.'});
783 if (captures.Length != 4)
786 for (int i = 0; i < 4; i++) {
789 length = captures [i].Length;
793 if (!UInt32.TryParse (captures [i], out number))
801 internal static bool IsDomainAddress (string name)
803 int len = name.Length;
806 for (int i = 0; i < len; i++) {
809 if (!Char.IsLetterOrDigit (c))
811 } else if (c == '.') {
812 // www..host.com is bad
813 if (i + 1 < len && name [i + 1] == '.')
817 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
828 [Obsolete("This method does nothing, it has been obsoleted")]
829 protected virtual void Canonicalize ()
832 // This is flagged in the Microsoft documentation as used
833 // internally, no longer in use, and Obsolete.
837 [MonoTODO ("Find out what this should do")]
839 protected virtual void CheckSecurity ()
845 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
846 public static bool CheckSchemeName (string schemeName)
848 if (schemeName == null || schemeName.Length == 0)
851 if (!IsAlpha (schemeName [0]))
854 int len = schemeName.Length;
855 for (int i = 1; i < len; i++) {
856 char c = schemeName [i];
857 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
864 private static bool IsAlpha (char c)
866 // as defined in rfc2234
867 // %x41-5A / %x61-7A (A-Z / a-z)
869 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
872 public override bool Equals (object comparand)
874 if (comparand == null)
877 Uri uri = comparand as Uri;
878 if ((object) uri == null) {
879 string s = comparand as String;
883 if (!TryCreate (s, UriKind.RelativeOrAbsolute, out uri))
887 return InternalEquals (uri);
890 // Assumes: uri != null
891 bool InternalEquals (Uri uri)
893 if (this.isAbsoluteUri != uri.isAbsoluteUri)
895 if (!this.isAbsoluteUri)
896 return this.source == uri.source;
898 CultureInfo inv = CultureInfo.InvariantCulture;
899 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
900 && this.host.ToLower (inv) == uri.host.ToLower (inv)
901 && this.port == uri.port
902 && this.query == uri.query
903 && this.path == uri.path;
906 public static bool operator == (Uri uri1, Uri uri2)
908 return object.Equals (uri1, uri2);
911 public static bool operator != (Uri uri1, Uri uri2)
913 return !(uri1 == uri2);
916 public override int GetHashCode ()
918 if (cachedHashCode == 0) {
919 CultureInfo inv = CultureInfo.InvariantCulture;
921 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
922 ^ host.ToLower (inv).GetHashCode ()
924 ^ query.GetHashCode ()
925 ^ path.GetHashCode ();
928 cachedHashCode = source.GetHashCode ();
931 return cachedHashCode;
934 public string GetLeftPart (UriPartial part)
936 EnsureAbsoluteUri ();
939 case UriPartial.Scheme :
940 return scheme + GetOpaqueWiseSchemeDelimiter ();
941 case UriPartial.Authority :
942 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
945 StringBuilder s = new StringBuilder ();
947 s.Append (GetOpaqueWiseSchemeDelimiter ());
948 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
949 s.Append ('/'); // win32 file
950 if (userinfo != null)
951 s.Append (userinfo).Append ('@');
953 defaultPort = GetDefaultPort (scheme);
954 if ((port != -1) && (port != defaultPort))
955 s.Append (':').Append (port);
956 return s.ToString ();
957 case UriPartial.Path :
958 StringBuilder sb = new StringBuilder ();
960 sb.Append (GetOpaqueWiseSchemeDelimiter ());
961 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
962 sb.Append ('/'); // win32 file
963 if (userinfo != null)
964 sb.Append (userinfo).Append ('@');
966 defaultPort = GetDefaultPort (scheme);
967 if ((port != -1) && (port != defaultPort))
968 sb.Append (':').Append (port);
970 if (path.Length > 0) {
971 if (scheme == "mailto" || scheme == "news")
974 sb.Append (Reduce (path, CompactEscaped (scheme)));
976 return sb.ToString ();
981 public static int FromHex (char digit)
983 if ('0' <= digit && digit <= '9') {
984 return (int) (digit - '0');
987 if ('a' <= digit && digit <= 'f')
988 return (int) (digit - 'a' + 10);
990 if ('A' <= digit && digit <= 'F')
991 return (int) (digit - 'A' + 10);
993 throw new ArgumentException ("digit");
996 public static string HexEscape (char character)
998 if (character > 255) {
999 throw new ArgumentOutOfRangeException ("character");
1002 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
1003 + hexUpperChars [((character & 0x0f))];
1006 public static char HexUnescape (string pattern, ref int index)
1008 if (pattern == null)
1009 throw new ArgumentException ("pattern");
1011 if (index < 0 || index >= pattern.Length)
1012 throw new ArgumentOutOfRangeException ("index");
1014 if (!IsHexEncoding (pattern, index))
1015 return pattern [index++];
1018 int msb = FromHex (pattern [index++]);
1019 int lsb = FromHex (pattern [index++]);
1020 return (char) ((msb << 4) | lsb);
1023 public static bool IsHexDigit (char character)
1025 return (('0' <= character && character <= '9') ||
1026 ('a' <= character && character <= 'f') ||
1027 ('A' <= character && character <= 'F'));
1030 public static bool IsHexEncoding (string pattern, int index)
1032 if ((index + 3) > pattern.Length)
1035 return ((pattern [index++] == '%') &&
1036 IsHexDigit (pattern [index++]) &&
1037 IsHexDigit (pattern [index]));
1041 // Implemented by copying most of the MakeRelative code
1043 public Uri MakeRelativeUri (Uri uri)
1047 throw new ArgumentNullException ("uri");
1049 if (Host != uri.Host || Scheme != uri.Scheme)
1052 string result = String.Empty;
1053 if (this.path != uri.path){
1054 string [] segments = this.Segments;
1055 string [] segments2 = uri.Segments;
1058 int max = Math.Min (segments.Length, segments2.Length);
1059 for (; k < max; k++)
1060 if (segments [k] != segments2 [k])
1063 for (int i = k; i < segments.Length && segments [i].EndsWith ("/"); i++)
1065 for (int i = k; i < segments2.Length; i++)
1066 result += segments2 [i];
1068 if (result == string.Empty)
1071 uri.AppendQueryAndFragment (ref result);
1073 return new Uri (result, UriKind.Relative);
1076 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1077 public string MakeRelative (Uri toUri)
1079 if ((this.Scheme != toUri.Scheme) ||
1080 (this.Authority != toUri.Authority))
1081 return toUri.ToString ();
1083 string result = String.Empty;
1084 if (this.path != toUri.path){
1085 string [] segments = this.Segments;
1086 string [] segments2 = toUri.Segments;
1088 int max = Math.Min (segments.Length, segments2.Length);
1089 for (; k < max; k++)
1090 if (segments [k] != segments2 [k])
1093 for (int i = k + 1; i < segments.Length; i++)
1095 for (int i = k; i < segments2.Length; i++)
1096 result += segments2 [i];
1099 // Important: MakeRelative does not append fragment or query.
1104 void AppendQueryAndFragment (ref string result)
1106 if (query.Length > 0) {
1107 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true, false) : Unescape (query, false);
1110 if (fragment.Length > 0)
1111 result += Unescape (fragment, true, false);
1114 public override string ToString ()
1116 if (cachedToString != null)
1117 return cachedToString;
1120 cachedToString = Parser.GetComponentsHelper (this, UriComponents.AbsoluteUri, UriHelper.ToStringUnescape);
1122 cachedToString = UriHelper.FormatRelative (source, scheme, UriHelper.ToStringUnescape);
1124 return cachedToString;
1127 protected void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
1129 if (this.isAbsoluteUri) {
1130 serializationInfo.AddValue ("AbsoluteUri", this.AbsoluteUri);
1132 serializationInfo.AddValue("AbsoluteUri", String.Empty);
1133 serializationInfo.AddValue("RelativeUri", this.OriginalString);
1137 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1139 GetObjectData (info, context);
1146 protected virtual void Escape ()
1148 path = EscapeString (path);
1152 protected static string EscapeString (string str)
1154 return EscapeString (str, Uri.EscapeCommonHexBrackets);
1157 private const string EscapeCommon = "<>%\"{}|\\^`";
1158 private const string EscapeReserved = ";/?:@&=+$,";
1159 private const string EscapeFragment = "#";
1160 private const string EscapeBrackets = "[]";
1162 private const string EscapeNews = EscapeCommon + EscapeBrackets + "?";
1163 private const string EscapeCommonHex = EscapeCommon + EscapeFragment;
1164 private const string EscapeCommonBrackets = EscapeCommon + EscapeBrackets;
1165 internal const string EscapeCommonHexBrackets = EscapeCommon + EscapeFragment + EscapeBrackets;
1166 internal const string EscapeCommonHexBracketsQuery = EscapeCommonHexBrackets + "?";
1168 internal static string EscapeString (string str, string escape)
1170 return EscapeString (str, escape, true);
1173 internal static string EscapeString (string str, string escape, bool nonAsciiEscape)
1175 if (String.IsNullOrEmpty (str))
1176 return String.Empty;
1178 StringBuilder s = new StringBuilder ();
1179 int len = str.Length;
1180 for (int i = 0; i < len; i++) {
1181 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1182 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1183 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1184 // space = <US-ASCII coded character 20 hexadecimal>
1185 // delims = "<" | ">" | "#" | "%" | <">
1186 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1188 // check for escape code already placed in str,
1189 // i.e. for encoding that follows the pattern
1190 // "%hexhex" in a string, where "hex" is a digit from 0-9
1191 // or a letter from A-F (case-insensitive).
1192 if (IsHexEncoding (str,i)) {
1193 // if ,yes , copy it as is
1194 s.Append(str.Substring (i, 3));
1200 bool outside_limited_ascii = ((c <= 0x20) || (c >= 0x7f));
1201 bool needs_escape = (escape.IndexOf (c) != -1);
1202 if (nonAsciiEscape && outside_limited_ascii) {
1203 byte [] data = Encoding.UTF8.GetBytes (new char [] { c });
1204 int length = data.Length;
1205 for (int j = 0; j < length; j++) {
1206 c = (char) data [j];
1207 if (needs_escape || nonAsciiEscape)
1208 s.Append (HexEscape (c));
1212 } else if (needs_escape) {
1213 s.Append (HexEscape (c));
1219 return s.ToString ();
1222 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1223 // can avoid the "absolute uri" constraints of the .ctor() by
1224 // overriding with custom code.
1225 [Obsolete("The method has been deprecated. It is not used by the system.")]
1226 protected virtual void Parse ()
1230 private void ParseUri (UriKind kind)
1232 Parse (kind, source);
1237 // non-ascii characters are not escaped for the host name
1238 host = EscapeString (host, EscapeCommonHex, false);
1239 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1240 // host name present (but not an IPv6 address)
1241 host = host.ToLower (CultureInfo.InvariantCulture);
1246 protected virtual string Unescape (string path)
1248 return Unescape (path, false, false);
1251 internal static string Unescape (string str, bool excludeSpecial)
1253 return Unescape (str, excludeSpecial, excludeSpecial);
1256 internal static string Unescape (string str, bool excludeSpecial, bool excludeBackslash)
1258 if (String.IsNullOrEmpty (str))
1259 return String.Empty;
1261 StringBuilder s = new StringBuilder ();
1262 int len = str.Length;
1263 for (int i = 0; i < len; i++) {
1267 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1268 if (excludeSpecial && x == '#')
1270 else if (excludeSpecial && x == '%')
1272 else if (excludeSpecial && x == '?')
1274 else if (excludeBackslash && x == '\\')
1278 if (surrogate != char.MinValue)
1279 s.Append (surrogate);
1285 return s.ToString ();
1291 private void ParseAsWindowsUNC (string uriString)
1293 scheme = UriSchemeFile;
1295 fragment = String.Empty;
1296 query = String.Empty;
1299 uriString = uriString.TrimStart (new char [] {'\\'});
1300 int pos = uriString.IndexOf ('\\');
1302 path = uriString.Substring (pos);
1303 host = uriString.Substring (0, pos);
1304 } else { // "\\\\server"
1306 path = String.Empty;
1308 path = path.Replace ("\\", "/");
1312 // Returns null on success, string with error on failure
1314 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1316 if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1317 return "Relative file path is not allowed.";
1318 scheme = UriSchemeFile;
1319 host = String.Empty;
1321 path = uriString.Replace ("\\", "/");
1322 fragment = String.Empty;
1323 query = String.Empty;
1328 private void ParseAsUnixAbsoluteFilePath (string uriString)
1330 isUnixFilePath = true;
1331 scheme = UriSchemeFile;
1333 fragment = String.Empty;
1334 query = String.Empty;
1335 host = String.Empty;
1338 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1339 uriString = uriString.TrimStart (new char [] {'/'});
1340 // Now we don't regard //foo/bar as "foo" host.
1342 int pos = uriString.IndexOf ('/');
1344 path = '/' + uriString.Substring (pos + 1);
1345 host = uriString.Substring (0, pos);
1346 } else { // "///server"
1348 path = String.Empty;
1351 path = '/' + uriString;
1358 // This parse method will throw exceptions on failure
1360 private void Parse (UriKind kind, string uriString)
1362 if (uriString == null)
1363 throw new ArgumentNullException ("uriString");
1365 string s = ParseNoExceptions (kind, uriString);
1367 throw new UriFormatException (s);
1370 private bool SupportsQuery ()
1372 return UriHelper.SupportsQuery (scheme);
1376 // This parse method will not throw exceptions on failure
1378 // Returns null on success, or a description of the error in the parsing
1380 private string ParseNoExceptions (UriKind kind, string uriString)
1385 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1389 uriString = uriString.Trim();
1390 int len = uriString.Length;
1393 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1394 isAbsoluteUri = false;
1399 if (len <= 1 && (kind == UriKind.Absolute))
1400 return "Absolute URI is too short";
1405 // Identify Windows path, unix path, or standard URI.
1406 if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1408 ParseAsUnixAbsoluteFilePath (uriString);
1409 if (kind == UriKind.Relative)
1410 isAbsoluteUri = false;
1412 } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\') {
1414 ParseAsWindowsUNC (uriString);
1419 pos = uriString.IndexOf (':');
1421 if (kind == UriKind.Absolute)
1422 return "Invalid URI: The format of the URI could not be determined.";
1423 isAbsoluteUri = false;
1426 } else if (pos < 0) {
1428 isAbsoluteUri = false;
1431 } else if (pos == 1) {
1432 if (!IsAlpha (uriString [0])) {
1433 if (kind == UriKind.Absolute)
1434 return "Invalid URI: The URI scheme is not valid.";
1435 isAbsoluteUri = false;
1439 // This means 'a:' == windows full path.
1440 string msg = ParseAsWindowsAbsoluteFilePath (uriString);
1447 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1449 // Check scheme name characters as specified in RFC2396.
1450 // Note: different checks in 1.x and 2.0
1451 if (!CheckSchemeName (scheme)) {
1452 if (kind == UriKind.Absolute)
1453 return "Invalid URI: The URI scheme is not valid.";
1454 isAbsoluteUri = false;
1459 scheme = TryGetKnownUriSchemeInstance (scheme);
1461 var formatFlags = UriHelper.FormatFlags.None;
1462 if (UriHelper.HasCharactersToNormalize (uriString))
1463 formatFlags |= UriHelper.FormatFlags.HasUriCharactersToNormalize;
1466 formatFlags |= UriHelper.FormatFlags.UserEscaped;
1468 // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1469 int startpos = pos + 1;
1470 int endpos = uriString.Length;
1473 pos = uriString.IndexOf ('#', startpos);
1474 if (!IsUnc && pos != -1) {
1475 fragment = uriString.Substring (pos);
1477 fragment = "#" + UriHelper.FormatAbsolute (fragment.Substring (1), scheme,
1478 UriComponents.Fragment, UriFormat.UriEscaped, formatFlags);
1481 // special case: there is no query part for 'news'
1482 if (scheme == Uri.UriSchemeNews) {
1483 pos = scheme.Length + 1;
1484 path = UriHelper.FormatAbsolute (uriString.Substring (pos, endpos - pos), scheme,
1485 UriComponents.Path, UriFormat.UriEscaped, formatFlags);
1489 // special case: there is no query part for 'nntp', 'file' and 'ftp' but there is an host, port, user...
1490 if (SupportsQuery ()) {
1492 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1494 query = uriString.Substring (pos, endpos-pos);
1496 query = "?" + UriHelper.FormatAbsolute (query.Substring (1), scheme,
1497 UriComponents.Query, UriFormat.UriEscaped, formatFlags);
1502 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && (
1503 (endpos-startpos < 2) ||
1504 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))
1505 return "Invalid URI: The Authority/Host could not be parsed.";
1508 bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1509 bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1510 bool windowsFilePath = false;
1511 if (startsWithSlashSlash) {
1512 if (kind == UriKind.Relative)
1513 return "Absolute URI when we expected a relative one";
1515 if (scheme != UriSchemeMailto)
1518 if (scheme == UriSchemeFile) {
1519 int num_leading_slash = 2;
1520 for (int i = startpos; i < endpos; i++) {
1521 if (uriString [i] != '/')
1523 num_leading_slash++;
1525 if (num_leading_slash >= 4) {
1526 unixAbsPath = false;
1527 while (startpos < endpos && uriString[startpos] == '/') {
1530 } else if (num_leading_slash >= 3) {
1535 if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
1536 unixAbsPath = false;
1537 windowsFilePath = true;
1540 } else if (!IsPredefinedScheme (scheme)) {
1541 path = uriString.Substring(startpos, endpos-startpos);
1542 path = UriHelper.FormatAbsolute (path, scheme,
1543 UriComponents.Path, UriFormat.UriEscaped, formatFlags);
1544 isOpaquePart = true;
1552 pos = uriString.IndexOf ('/', startpos, endpos - startpos);
1553 if (pos == -1 && windowsFilePath)
1554 pos = uriString.IndexOf ('\\', startpos, endpos - startpos);
1557 path = uriString.Substring (pos, endpos - pos);
1560 if (scheme != Uri.UriSchemeMailto)
1568 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1570 // supplying username / password on a file URI is not supported
1571 if (scheme == UriSchemeFile)
1572 return "Invalid host";
1573 userinfo = uriString.Substring (startpos, pos-startpos);
1578 bool valid_port = true;
1583 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1584 if (pos != -1 && pos != endpos - 1) {
1585 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1586 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1587 if (!Int32.TryParse (portStr, NumberStyles.None, CultureInfo.InvariantCulture, out port) ||
1588 port < 0 || port > UInt16.MaxValue)
1589 valid_port = false; // delay reporting
1594 port = GetDefaultPort (scheme);
1597 } else if (!IsFile) {
1598 // if no port is specified by a colon ':' is present then we must ignore it
1599 // since it would be part of the host name and, as such, would be invalid
1600 if (pos == endpos - 1)
1604 port = GetDefaultPort (scheme);
1608 uriString = uriString.Substring(startpos, endpos-startpos);
1612 path = Reduce ('/' + uriString, true);
1613 host = String.Empty;
1614 } else if (host.Length == 2 && host [1] == ':') {
1615 if (scheme != UriSchemeFile) {
1616 host = host [0].ToString ();
1620 host = String.Empty;
1622 } else if (isUnixFilePath) {
1623 uriString = "//" + uriString;
1624 host = String.Empty;
1625 } else if (scheme == UriSchemeFile) {
1626 // under Windows all file:// URI are considered UNC, which is not the case other MacOS (e.g. Silverlight)
1630 isUnc = Environment.IsRunningOnWindows;
1632 } else if (host.Length == 0 &&
1633 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
1634 scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1635 return "Invalid URI: The Authority/Host could not be parsed.";
1638 if (host.Length > 0) {
1639 switch (CheckHostName (host)) {
1640 case UriHostNameType.Unknown:
1641 if ((host [0] == ':') || (host [0] == '@'))
1642 return "Invalid URI: The hostname could not be parsed.";
1643 if (host.IndexOf (':') != -1)
1644 return "Invalid URI: Invalid port specified.";
1645 if (Parser is DefaultUriParser || Parser == null)
1646 return "Invalid URI: The hostname could not be parsed.";
1648 case UriHostNameType.IPv6:
1649 IPv6Address ipv6addr;
1650 if (IPv6Address.TryParse (host, out ipv6addr)) {
1651 host = "[" + ipv6addr.ToString (!IriParsing) + "]";
1652 scope_id = ipv6addr.ScopeId;
1656 formatFlags |= UriHelper.FormatFlags.HasHost;
1658 // delayed reporting (to throw the expected exception in the right order)
1660 return "Invalid URI: Invalid port number";
1662 UriFormatException ex = null;
1664 Parser.InitializeAndValidate (this, out ex);
1668 path = UriHelper.FormatAbsolute (path, scheme,
1669 UriComponents.Path, UriFormat.UriEscaped, formatFlags);
1674 private static string TryGetKnownUriSchemeInstance (string scheme)
1676 foreach (string knownScheme in knownUriSchemes) {
1677 if (knownScheme == scheme)
1684 private static bool CompactEscaped (string scheme)
1686 if (scheme == null || scheme.Length < 4)
1689 char first = scheme [0];
1691 return scheme == "http" || scheme == "https";
1692 } else if (first == 'f' && scheme == "file"){
1694 } else if (first == 'n')
1695 return scheme == "net.pipe" || scheme == "net.tcp";
1700 // replace '\', %5C ('\') and %2f ('/') into '/'
1701 // replace %2e ('.') into '.'
1702 private static string NormalizePath (string path)
1704 StringBuilder res = new StringBuilder ();
1705 for (int i = 0; i < path.Length; i++) {
1712 if (i < path.Length - 2) {
1713 char c1 = path [i + 1];
1714 char c2 = Char.ToUpper (path [i + 2]);
1715 if ((c1 == '2') && (c2 == 'E')) {
1718 } else if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1727 return res.ToString ();
1730 // This is called "compacting" in the MSDN documentation
1731 private static string Reduce (string path, bool compact_escaped)
1733 // quick out, allocation-free, for a common case
1737 if (compact_escaped && (path.IndexOf ('%') != -1)) {
1738 // replace '\', %2f, %5c with '/' and replace %2e with '.'
1739 // other escaped values seems to survive this step
1740 path = NormalizePath (path);
1742 // (always) replace '\' with '/'
1743 path = path.Replace ('\\', '/');
1746 List<string> result = new List<string> ();
1749 for (int startpos = 0; startpos < path.Length; ) {
1750 int endpos = path.IndexOf ('/', startpos);
1752 endpos = path.Length;
1753 string current = path.Substring (startpos, endpos-startpos);
1754 startpos = endpos + 1;
1755 if ((begin && current.Length == 0) || current == "." ) {
1761 if (current == "..") {
1762 int resultCount = result.Count;
1763 // in 2.0 profile, skip leading ".." parts
1764 if (resultCount == 0) {
1768 result.RemoveAt (resultCount - 1);
1772 result.Add (current);
1775 if (result.Count == 0)
1778 StringBuilder res = new StringBuilder ();
1780 if (path [0] == '/')
1784 foreach (string part in result) {
1793 if (path [path.Length - 1] == '/')
1796 return res.ToString();
1799 // A variant of HexUnescape() which can decode multi-byte escaped
1800 // sequences such as (e.g.) %E3%81%8B into a single character
1801 internal static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1803 surrogate = char.MinValue;
1805 if (pattern == null)
1806 throw new ArgumentException ("pattern");
1808 if (index < 0 || index >= pattern.Length)
1809 throw new ArgumentOutOfRangeException ("index");
1811 if (!IsHexEncoding (pattern, index))
1812 return pattern [index++];
1814 int orig_index = index++;
1815 int msb = FromHex (pattern [index++]);
1816 int lsb = FromHex (pattern [index++]);
1818 // We might be dealing with a multi-byte character:
1819 // The number of ones at the top-end of the first byte will tell us
1820 // how many bytes will make up this character.
1823 while ((msb_copy & 0x8) == 0x8) {
1828 // We might be dealing with a single-byte character:
1829 // If there was only 0 or 1 leading ones then we're not dealing
1830 // with a multi-byte character.
1832 return (char) ((msb << 4) | lsb);
1834 // Now that we know how many bytes *should* follow, we'll check them
1835 // to ensure we are dealing with a valid multi-byte character.
1836 byte [] chars = new byte [num_bytes];
1837 bool all_invalid = false;
1838 chars[0] = (byte) ((msb << 4) | lsb);
1840 for (int i = 1; i < num_bytes; i++) {
1841 if (!IsHexEncoding (pattern, index++)) {
1846 // All following bytes must be in the form 10xxxxxx
1847 int cur_msb = FromHex (pattern [index++]);
1848 if ((cur_msb & 0xc) != 0x8) {
1853 int cur_lsb = FromHex (pattern [index++]);
1854 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1857 // If what looked like a multi-byte character is invalid, then we'll
1858 // just return the first byte as a single byte character.
1860 index = orig_index + 3;
1861 return (char) chars[0];
1864 // Otherwise, we're dealing with a valid multi-byte character.
1865 // We need to ignore the leading ones from the first byte:
1866 byte mask = (byte) 0xFF;
1867 mask >>= (num_bytes + 1);
1868 int result = chars[0] & mask;
1870 // The result will now be built up from the following bytes.
1871 for (int i = 1; i < num_bytes; i++) {
1872 // Ignore upper two bits
1874 result |= (chars[i] & 0x3F);
1877 if (result <= 0xFFFF) {
1878 return (char) result;
1880 // We need to handle this as a UTF16 surrogate (i.e. return
1883 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1884 return (char) ((result >> 10) | 0xD800);
1888 private struct UriScheme
1890 public string scheme;
1891 public string delimiter;
1892 public int defaultPort;
1894 public UriScheme (string s, string d, int p)
1902 static UriScheme [] schemes = new UriScheme [] {
1903 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1904 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1905 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1906 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1907 new UriScheme (UriSchemeMailto, ":", 25),
1908 new UriScheme (UriSchemeNews, ":", 119),
1909 new UriScheme (UriSchemeUuid, ":", -1),
1910 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1911 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1914 internal static string GetSchemeDelimiter (string scheme)
1916 for (int i = 0; i < schemes.Length; i++)
1917 if (schemes [i].scheme == scheme)
1918 return schemes [i].delimiter;
1919 return Uri.SchemeDelimiter;
1922 internal static int GetDefaultPort (string scheme)
1924 UriParser parser = UriParser.GetParser (scheme);
1927 return parser.DefaultPort;
1930 private string GetOpaqueWiseSchemeDelimiter ()
1935 return GetSchemeDelimiter (scheme);
1939 protected virtual bool IsBadFileSystemCharacter (char character)
1941 // It does not always overlap with InvalidPathChars.
1942 int chInt = (int) character;
1943 if (chInt < 32 || (chInt < 64 && chInt > 57))
1962 protected static bool IsExcludedCharacter (char character)
1964 if (character <= 32 || character >= 127)
1967 if (character == '"' || character == '#' || character == '%' || character == '<' ||
1968 character == '>' || character == '[' || character == '\\' || character == ']' ||
1969 character == '^' || character == '`' || character == '{' || character == '|' ||
1975 internal static bool MaybeUri (string s)
1977 int p = s.IndexOf (':');
1984 return IsPredefinedScheme (s.Substring (0, p));
1988 // Using a simple block of if's is twice as slow as the compiler generated
1989 // switch statement. But using this tuned code is faster than the
1990 // compiler generated code, with a million loops on x86-64:
1992 // With "http": .10 vs .51 (first check)
1993 // with "https": .16 vs .51 (second check)
1994 // with "foo": .22 vs .31 (never found)
1995 // with "mailto": .12 vs .51 (last check)
1998 private static bool IsPredefinedScheme (string scheme)
2000 if (scheme == null || scheme.Length < 3)
2003 char c = scheme [0];
2005 return (scheme == "http" || scheme == "https");
2007 return (scheme == "file" || scheme == "ftp");
2012 return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
2013 if (scheme == "nntp")
2017 if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
2024 protected virtual bool IsReservedCharacter (char character)
2026 if (character == '$' || character == '&' || character == '+' || character == ',' ||
2027 character == '/' || character == ':' || character == ';' || character == '=' ||
2034 private UriParser parser;
2036 private UriParser Parser {
2038 if (parser == null) {
2039 parser = UriParser.GetParser (Scheme);
2040 // no specific parser ? then use a default one
2042 parser = new DefaultUriParser ("*");
2046 set { parser = value; }
2049 public string GetComponents (UriComponents components, UriFormat format)
2051 return Parser.GetComponents (this, components, format);
2054 public bool IsBaseOf (Uri uri)
2058 throw new ArgumentNullException ("uri");
2060 return Parser.IsBaseOf (this, uri);
2063 public bool IsWellFormedOriginalString ()
2065 // funny, but it does not use the Parser's IsWellFormedOriginalString().
2066 // Also, it seems we need to *not* escape hex.
2067 return EscapeString (OriginalString, EscapeCommonBrackets) == OriginalString;
2072 private const int MaxUriLength = 32766;
2074 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
2076 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
2077 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
2078 throw new ArgumentException ("comparisonType", msg);
2081 if ((uri1 == null) && (uri2 == null))
2084 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
2085 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
2086 return String.Compare (s1, s2, comparisonType);
2090 // The rules for EscapeDataString
2092 static bool NeedToEscapeDataChar (char b)
2095 // .NET 4.0 follows RFC 3986 Unreserved Characters
2096 return !((b >= 'A' && b <= 'Z') ||
2097 (b >= 'a' && b <= 'z') ||
2098 (b >= '0' && b <= '9') ||
2099 b == '-' || b == '.' || b == '_' || b == '~');
2102 return !((b >= 'A' && b <= 'Z') ||
2103 (b >= 'a' && b <= 'z') ||
2104 (b >= '0' && b <= '9') ||
2105 b == '_' || b == '~' || b == '!' || b == '\'' ||
2106 b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
2109 public static string EscapeDataString (string stringToEscape)
2111 if (stringToEscape == null)
2112 throw new ArgumentNullException ("stringToEscape");
2114 if (stringToEscape.Length > MaxUriLength) {
2115 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
2118 bool escape = false;
2119 foreach (char c in stringToEscape){
2120 if (NeedToEscapeDataChar (c)){
2126 return stringToEscape;
2129 StringBuilder sb = new StringBuilder ();
2130 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2131 foreach (byte b in bytes){
2132 if (NeedToEscapeDataChar ((char) b))
2133 sb.Append (HexEscape ((char) b));
2135 sb.Append ((char) b);
2137 return sb.ToString ();
2141 // The rules for EscapeUriString
2143 static bool NeedToEscapeUriChar (char b)
2145 if ((b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '&' && b <= ';'))
2148 if ("!#$=?@_~".IndexOf(b) != -1)
2152 if ("[]".IndexOf(b) != -1)
2158 public static string EscapeUriString (string stringToEscape)
2160 if (stringToEscape == null)
2161 throw new ArgumentNullException ("stringToEscape");
2163 if (stringToEscape.Length > MaxUriLength) {
2164 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
2167 bool escape = false;
2168 foreach (char c in stringToEscape){
2169 if (NeedToEscapeUriChar (c)){
2175 return stringToEscape;
2177 StringBuilder sb = new StringBuilder ();
2178 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2179 foreach (byte b in bytes){
2180 if (NeedToEscapeUriChar ((char) b))
2181 sb.Append (HexEscape ((char) b));
2183 sb.Append ((char) b);
2185 return sb.ToString ();
2188 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
2190 if (uriString == null)
2194 if (Uri.TryCreate (uriString, uriKind, out uri))
2195 return uri.IsWellFormedOriginalString ();
2199 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
2203 Uri r = new Uri (uriString, uriKind, out success);
2212 // [MonoTODO ("rework code to avoid exception catching")]
2213 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
2216 if (relativeUri == null)
2220 Uri relative = new Uri (relativeUri, UriKind.RelativeOrAbsolute);
2221 if ((baseUri != null) && baseUri.IsAbsoluteUri) {
2222 // FIXME: this should call UriParser.Resolve
2223 result = new Uri (baseUri, relative);
2224 } else if (relative.IsAbsoluteUri) {
2225 // special case - see unit tests
2228 return (result != null);
2229 } catch (UriFormatException) {
2234 //[MonoTODO ("rework code to avoid exception catching")]
2235 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
2238 if ((baseUri == null) || !baseUri.IsAbsoluteUri)
2241 if (relativeUri == null)
2245 // FIXME: this should call UriParser.Resolve
2246 result = new Uri (baseUri, relativeUri.OriginalString);
2248 } catch (UriFormatException) {
2253 public static string UnescapeDataString (string stringToUnescape)
2255 return UnescapeDataString (stringToUnescape, false);
2258 internal static string UnescapeDataString (string stringToUnescape, bool safe)
2260 if (stringToUnescape == null)
2261 throw new ArgumentNullException ("stringToUnescape");
2263 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
2264 return stringToUnescape;
2266 StringBuilder output = new StringBuilder ();
2267 long len = stringToUnescape.Length;
2268 MemoryStream bytes = new MemoryStream ();
2271 for (int i = 0; i < len; i++) {
2272 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
2273 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
2274 if (bytes.Length > 0) {
2275 output.Append (GetChars (bytes, Encoding.UTF8));
2276 bytes.SetLength (0);
2279 xchar = GetChar (stringToUnescape, i + 2, 4, safe);
2281 output.Append ((char) xchar);
2285 output.Append ('%');
2288 else if ((xchar = GetChar (stringToUnescape, i + 1, 2, safe)) != -1) {
2289 bytes.WriteByte ((byte) xchar);
2293 output.Append ('%');
2298 if (bytes.Length > 0) {
2299 output.Append (GetChars (bytes, Encoding.UTF8));
2300 bytes.SetLength (0);
2303 output.Append (stringToUnescape [i]);
2306 if (bytes.Length > 0) {
2307 output.Append (GetChars (bytes, Encoding.UTF8));
2311 return output.ToString ();
2314 private static int GetInt (byte b)
2317 if (c >= '0' && c <= '9')
2320 if (c >= 'a' && c <= 'f')
2321 return c - 'a' + 10;
2323 if (c >= 'A' && c <= 'F')
2324 return c - 'A' + 10;
2329 private static int GetChar (string str, int offset, int length, bool safe)
2332 int end = length + offset;
2333 for (int i = offset; i < end; i++) {
2338 int current = GetInt ((byte) c);
2341 val = (val << 4) + current;
2347 switch ((char) val) {
2354 case '&': // not documented
2361 private static char [] GetChars (MemoryStream b, Encoding e)
2363 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2367 private void EnsureAbsoluteUri ()
2370 throw new InvalidOperationException ("This operation is not supported for a relative URI.");