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 string source;
73 private string scheme = String.Empty;
74 private string host = String.Empty;
75 private int port = -1;
76 private string path = String.Empty;
77 private string query = String.Empty;
78 private string fragment = String.Empty;
79 private string userinfo;
81 private bool isAbsoluteUri = true;
82 private long scope_id;
84 private List<string> segments;
86 private bool userEscaped;
87 private string cachedAbsoluteUri;
88 private string cachedToString;
89 private string cachedLocalPath;
90 private int cachedHashCode;
92 private static bool s_IriParsing;
94 internal static bool IriParsing {
95 get { return s_IriParsing; }
96 set { s_IriParsing = value; }
99 // Do not rename this.
100 // User code might set this to true with reflection.
101 // When set to true an Uri constructed with UriKind.RelativeOrAbsolute
102 // and paths such as "/foo" is assumed relative.
103 private static bool useDotNetRelativeOrAbsolute;
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 =
151 var iriparsingVar = Environment.GetEnvironmentVariable ("MONO_URI_IRIPARSING");
152 if (iriparsingVar == "true")
154 else if (iriparsingVar == "false")
157 useDotNetRelativeOrAbsolute = Environment.GetEnvironmentVariable ("MONO_URI_DOTNETRELATIVEORABSOLUTE") == "true";
160 public Uri (string uriString) : this (uriString, false)
164 protected Uri (SerializationInfo serializationInfo, StreamingContext streamingContext)
166 string uri = serializationInfo.GetString ("AbsoluteUri");
167 if (uri.Length > 0) {
169 ParseUri (UriKind.Absolute);
171 uri = serializationInfo.GetString ("RelativeUri");
172 if (uri.Length > 0) {
174 ParseUri (UriKind.Relative);
176 throw new ArgumentException ("Uri string was null or empty.");
181 // When used instead of UriKind.RelativeOrAbsolute paths such as "/foo" are assumed relative.
182 const UriKind DotNetRelativeOrAbsolute = (UriKind) 300;
184 private void ProcessUriKind (string uriString, ref UriKind uriKind)
186 if (uriString == null)
189 if (uriKind == DotNetRelativeOrAbsolute ||
190 (uriKind == UriKind.RelativeOrAbsolute && useDotNetRelativeOrAbsolute))
191 uriKind = (uriString.StartsWith ("/", StringComparison.Ordinal))? UriKind.Relative : UriKind.RelativeOrAbsolute;
194 public Uri (string uriString, UriKind uriKind)
198 ProcessUriKind (uriString, ref uriKind);
203 case UriKind.Absolute:
205 throw new UriFormatException ("Invalid URI: The format of the URI could not be "
208 case UriKind.Relative:
210 throw new UriFormatException ("Invalid URI: The format of the URI could not be "
211 + "determined because the parameter 'uriString' represents an absolute URI.");
213 case UriKind.RelativeOrAbsolute:
216 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
217 throw new ArgumentException (msg);
222 // An exception-less constructor, returns success
223 // condition on the out parameter `success'.
225 Uri (string uriString, UriKind uriKind, out bool success)
227 if (uriString == null) {
232 ProcessUriKind (uriString, ref uriKind);
234 if (uriKind != UriKind.RelativeOrAbsolute &&
235 uriKind != UriKind.Absolute &&
236 uriKind != UriKind.Relative) {
237 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
238 throw new ArgumentException (msg);
242 if (ParseNoExceptions (uriKind, uriString) != null)
248 case UriKind.Absolute:
252 case UriKind.Relative:
256 case UriKind.RelativeOrAbsolute:
263 if (success && host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
264 // host name present (but not an IPv6 address)
265 host = host.ToLower (CultureInfo.InvariantCulture);
270 public Uri (Uri baseUri, Uri relativeUri)
272 Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
273 // FIXME: this should call UriParser.Resolve
276 // note: doc says that dontEscape is always false but tests show otherwise
278 public Uri (string uriString, bool dontEscape)
280 userEscaped = dontEscape;
282 ParseUri (UriKind.Absolute);
284 throw new UriFormatException ("Invalid URI: The format of the URI could not be "
285 + "determined: " + uriString);
288 public Uri (Uri baseUri, string relativeUri)
290 Merge (baseUri, relativeUri);
291 // FIXME: this should call UriParser.Resolve
294 [Obsolete ("dontEscape is always false")]
295 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
297 userEscaped = dontEscape;
298 Merge (baseUri, relativeUri);
301 private void Merge (Uri baseUri, string relativeUri)
304 throw new ArgumentNullException ("baseUri");
305 if (!baseUri.IsAbsoluteUri)
306 throw new ArgumentOutOfRangeException ("baseUri");
307 if (string.IsNullOrEmpty (relativeUri)) {
308 source = baseUri.OriginalString;
309 ParseUri (UriKind.Absolute);
314 bool startsWithSlash = false;
317 if (!UriParseComponents.TryParseComponents (baseUri.OriginalString, UriKind.Absolute, out baseEl, out error))
318 throw new UriFormatException (error);
320 if (relativeUri.StartsWith (baseEl.scheme + ":", StringComparison.Ordinal))
321 relativeUri = relativeUri.Substring (baseEl.scheme.Length + 1);
323 if (relativeUri.Length >= 1 && relativeUri [0] == '/') {
324 if (relativeUri.Length >= 2 && relativeUri [1] == '/') {
325 source = baseEl.scheme + ":" + relativeUri;
326 ParseUri (UriKind.Absolute);
330 relativeUri = relativeUri.Substring (1);
331 startsWithSlash = true;
334 UriElements relativeEl;
335 if (!UriParseComponents.TryParseComponents (relativeUri, UriKind.RelativeOrAbsolute, out relativeEl, out error))
336 throw new UriFormatException (error);
338 if (relativeEl.isAbsoluteUri) {
339 source = relativeUri;
340 ParseUri (UriKind.Absolute);
344 source = baseEl.scheme + baseEl.delimiter;
346 if (baseEl.user != null)
347 source += baseEl.user + "@";
349 source += baseEl.host;
351 if (baseEl.port >= 0)
352 source += ":" + baseEl.port.ToString (CultureInfo.InvariantCulture);
354 var canUseBase = true;
357 if (!string.IsNullOrEmpty (relativeEl.path) || startsWithSlash) {
359 path = relativeEl.path;
361 path = relativeEl.path;
363 var pathEnd = baseEl.path.LastIndexOf ('/');
364 path = (pathEnd > 0)? baseEl.path.Substring (0, pathEnd+1) : "";
365 path += relativeEl.path;
371 if ((path.Length == 0 || path [0] != '/') && baseEl.delimiter == SchemeDelimiter)
374 source += UriHelper.Reduce (path, !IriParsing);
376 if (relativeEl.query != null) {
378 source += "?" + relativeEl.query;
379 } else if (canUseBase && baseEl.query != null)
380 source += "?" + baseEl.query;
382 if (relativeEl.fragment != null)
383 source += "#" + relativeEl.fragment;
384 else if (canUseBase && baseEl.fragment != null)
385 source += "#" + baseEl.fragment;
387 ParseUri (UriKind.Absolute);
394 public string AbsolutePath {
396 EnsureAbsoluteUri ();
397 if (scheme == "mailto" || scheme == "file")
398 // faster (mailto) and special (file) cases
401 if (path.Length == 0) {
402 string start = scheme + SchemeDelimiter;
403 if (path.StartsWith (start, StringComparison.Ordinal))
412 public string AbsoluteUri {
414 EnsureAbsoluteUri ();
416 if (cachedAbsoluteUri == null)
417 cachedAbsoluteUri = GetComponents (UriComponents.AbsoluteUri, UriFormat.UriEscaped);
419 return cachedAbsoluteUri;
423 public string Authority {
425 EnsureAbsoluteUri ();
426 return (GetDefaultPort (Scheme) == port)
427 ? host : host + ":" + port;
431 public string Fragment {
433 EnsureAbsoluteUri ();
440 EnsureAbsoluteUri ();
445 public UriHostNameType HostNameType {
447 EnsureAbsoluteUri ();
448 UriHostNameType ret = CheckHostName (Host);
449 if (ret != UriHostNameType.Unknown)
452 if (scheme == "mailto")
453 return UriHostNameType.Basic;
454 return (IsFile) ? UriHostNameType.Basic : ret;
458 public bool IsDefaultPort {
460 EnsureAbsoluteUri ();
461 return GetDefaultPort (Scheme) == port;
467 EnsureAbsoluteUri ();
468 return (Scheme == UriSchemeFile);
472 public bool IsLoopback {
474 EnsureAbsoluteUri ();
476 if (Host.Length == 0) {
480 if (host == "loopback" || host == "localhost")
484 if (IPAddress.TryParse (host, out result))
485 if (IPAddress.Loopback.Equals (result))
489 if (IPv6Address.TryParse (host, out result6)){
490 if (IPv6Address.IsLoopback (result6))
499 // rule: This should be true only if
500 // - uri string starts from "\\", or
501 // - uri string starts from "//" (Samba way)
503 EnsureAbsoluteUri ();
508 private bool IsLocalIdenticalToAbsolutePath ()
513 if ((scheme == Uri.UriSchemeNews) || (scheme == Uri.UriSchemeNntp) || (scheme == Uri.UriSchemeFtp))
516 return IsWellFormedOriginalString ();
519 public string LocalPath {
521 EnsureAbsoluteUri ();
522 if (cachedLocalPath != null)
523 return cachedLocalPath;
525 var formatFlags = UriHelper.FormatFlags.NoSlashReplace;
528 formatFlags |= UriHelper.FormatFlags.UserEscaped;
530 string unescapedPath = UriHelper.FormatAbsolute (path, scheme,
531 UriComponents.Path, UriFormat.Unescaped, formatFlags);
533 if (path.StartsWith ("/", StringComparison.Ordinal) &&
534 !unescapedPath.StartsWith ("/", StringComparison.Ordinal))
535 unescapedPath = "/" + unescapedPath;
537 if (IsLocalIdenticalToAbsolutePath ()) {
538 cachedLocalPath = unescapedPath;
539 return cachedLocalPath;
543 bool windows = (path.Length > 3 && path [1] == ':' &&
544 (path [2] == '\\' || path [2] == '/'));
547 cachedLocalPath = unescapedPath.Replace ('/', '\\');
549 cachedLocalPath = unescapedPath;
551 // support *nix and W32 styles
552 if (path.Length > 1 && path [1] == ':')
553 cachedLocalPath = unescapedPath.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
555 // LAMESPEC: ok, now we cannot determine
556 // if such URI like "file://foo/bar" is
557 // Windows UNC or unix file path, so
558 // they should be handled differently.
559 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
561 if (path.Length > 0) {
562 if ((path.Length > 1) || (path[0] != '/')) {
563 h += unescapedPath.Replace ('/', '\\');
566 cachedLocalPath = "\\\\" + h;
568 cachedLocalPath = unescapedPath;
570 if (cachedLocalPath.Length == 0)
571 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
572 return cachedLocalPath;
576 public string PathAndQuery {
578 EnsureAbsoluteUri ();
585 EnsureAbsoluteUri ();
590 public string Query {
592 EnsureAbsoluteUri ();
597 public string Scheme {
599 EnsureAbsoluteUri ();
604 public string [] Segments {
606 EnsureAbsoluteUri ();
608 // return a (pre-allocated) empty array
609 if (path.Length == 0)
613 return EmptyArray<string>.Value;
615 // do not return the original array (since items can be changed)
616 if (segments != null)
617 return segments.ToArray ();
619 List<string> list = new List<string> ();
620 StringBuilder current = new StringBuilder ();
621 for (int i = 0; i < path.Length; i++) {
625 current.Append (path [i]);
626 list.Add (current.ToString ());
630 if ((i < path.Length - 2) && (path [i + 1] == '5' && path [i + 2] == 'C')) {
631 current.Append ("%5C");
632 list.Add (current.ToString ());
636 current.Append ('%');
640 current.Append (path [i]);
645 if (current.Length > 0)
646 list.Add (current.ToString ());
648 if (IsFile && (list.Count > 0)) {
649 string first = list [0];
650 if ((first.Length > 1) && (first [1] == ':')) {
651 list.Insert (0, "/");
655 return segments.ToArray ();
659 public bool UserEscaped {
660 get { return userEscaped; }
663 public string UserInfo {
665 EnsureAbsoluteUri ();
666 return userinfo == null ? String.Empty : userinfo;
670 public string DnsSafeHost {
672 EnsureAbsoluteUri ();
674 if (HostNameType == UriHostNameType.IPv6) {
675 host = Host.Substring (1, Host.Length - 2);
677 host += "%" + scope_id.ToString ();
679 return Unescape (host);
683 public bool IsAbsoluteUri {
684 get { return isAbsoluteUri; }
687 public string OriginalString {
688 get { return source; }
693 public static UriHostNameType CheckHostName (string name)
695 if (name == null || name.Length == 0)
696 return UriHostNameType.Unknown;
698 if (IsIPv4Address (name))
699 return UriHostNameType.IPv4;
701 if (IsDomainAddress (name))
702 return UriHostNameType.Dns;
705 if (IPv6Address.TryParse (name, out addr))
706 return UriHostNameType.IPv6;
708 return UriHostNameType.Unknown;
711 internal static bool IsIPv4Address (string name)
713 string [] captures = name.Split (new char [] {'.'});
714 if (captures.Length != 4)
717 for (int i = 0; i < 4; i++) {
720 length = captures [i].Length;
724 if (!UInt32.TryParse (captures [i], out number))
732 internal static bool IsDomainAddress (string name)
734 int len = name.Length;
737 for (int i = 0; i < len; i++) {
740 if (!Char.IsLetterOrDigit (c))
742 } else if (c == '.') {
743 // www..host.com is bad
744 if (i + 1 < len && name [i + 1] == '.')
748 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
759 [Obsolete ("This method does nothing, it has been obsoleted")]
760 protected virtual void Canonicalize ()
763 // This is flagged in the Microsoft documentation as used
764 // internally, no longer in use, and Obsolete.
768 [MonoTODO ("Find out what this should do")]
770 protected virtual void CheckSecurity ()
776 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
777 public static bool CheckSchemeName (string schemeName)
779 if (schemeName == null || schemeName.Length == 0)
782 if (!IsAlpha (schemeName [0]))
785 int len = schemeName.Length;
786 for (int i = 1; i < len; i++) {
787 char c = schemeName [i];
788 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
795 private static bool IsAlpha (char c)
797 // as defined in rfc2234
798 // %x41-5A / %x61-7A (A-Z / a-z)
800 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
803 public override bool Equals (object comparand)
805 if (comparand == null)
808 Uri uri = comparand as Uri;
809 if ((object) uri == null) {
810 string s = comparand as String;
814 if (!TryCreate (s, UriKind.RelativeOrAbsolute, out uri))
818 return InternalEquals (uri);
821 // Assumes: uri != null
822 bool InternalEquals (Uri uri)
824 if (this.isAbsoluteUri != uri.isAbsoluteUri)
826 if (!this.isAbsoluteUri)
827 return this.source == uri.source;
829 CultureInfo inv = CultureInfo.InvariantCulture;
830 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
831 && this.host.ToLower (inv) == uri.host.ToLower (inv)
832 && this.port == uri.port
833 && this.query == uri.query
834 && this.path == uri.path;
837 public static bool operator == (Uri uri1, Uri uri2)
839 return object.Equals (uri1, uri2);
842 public static bool operator != (Uri uri1, Uri uri2)
844 return !(uri1 == uri2);
847 public override int GetHashCode ()
849 if (cachedHashCode == 0) {
850 CultureInfo inv = CultureInfo.InvariantCulture;
852 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
853 ^ host.ToLower (inv).GetHashCode ()
855 ^ query.GetHashCode ()
856 ^ path.GetHashCode ();
859 cachedHashCode = source.GetHashCode ();
862 return cachedHashCode;
865 public string GetLeftPart (UriPartial part)
867 EnsureAbsoluteUri ();
870 case UriPartial.Scheme :
871 return scheme + GetOpaqueWiseSchemeDelimiter ();
872 case UriPartial.Authority :
873 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
876 StringBuilder s = new StringBuilder ();
878 s.Append (GetOpaqueWiseSchemeDelimiter ());
879 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
880 s.Append ('/'); // win32 file
881 if (userinfo != null)
882 s.Append (userinfo).Append ('@');
884 defaultPort = GetDefaultPort (scheme);
885 if ((port != -1) && (port != defaultPort))
886 s.Append (':').Append (port);
887 return s.ToString ();
888 case UriPartial.Path :
889 StringBuilder sb = new StringBuilder ();
891 sb.Append (GetOpaqueWiseSchemeDelimiter ());
892 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
893 sb.Append ('/'); // win32 file
894 if (userinfo != null)
895 sb.Append (userinfo).Append ('@');
897 defaultPort = GetDefaultPort (scheme);
898 if ((port != -1) && (port != defaultPort))
899 sb.Append (':').Append (port);
901 if (path.Length > 0) {
902 if (scheme == "mailto" || scheme == "news")
905 sb.Append (Reduce (path, CompactEscaped (scheme)));
907 return sb.ToString ();
912 public static int FromHex (char digit)
914 if ('0' <= digit && digit <= '9') {
915 return (int) (digit - '0');
918 if ('a' <= digit && digit <= 'f')
919 return (int) (digit - 'a' + 10);
921 if ('A' <= digit && digit <= 'F')
922 return (int) (digit - 'A' + 10);
924 throw new ArgumentException ("digit");
927 public static string HexEscape (char character)
929 if (character > 255) {
930 throw new ArgumentOutOfRangeException ("character");
933 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
934 + hexUpperChars [((character & 0x0f))];
937 public static char HexUnescape (string pattern, ref int index)
940 throw new ArgumentException ("pattern");
942 if (index < 0 || index >= pattern.Length)
943 throw new ArgumentOutOfRangeException ("index");
945 if (!IsHexEncoding (pattern, index))
946 return pattern [index++];
949 int msb = FromHex (pattern [index++]);
950 int lsb = FromHex (pattern [index++]);
951 return (char) ((msb << 4) | lsb);
954 public static bool IsHexDigit (char character)
956 return (('0' <= character && character <= '9') ||
957 ('a' <= character && character <= 'f') ||
958 ('A' <= character && character <= 'F'));
961 public static bool IsHexEncoding (string pattern, int index)
963 if ((index + 3) > pattern.Length)
966 return ((pattern [index++] == '%') &&
967 IsHexDigit (pattern [index++]) &&
968 IsHexDigit (pattern [index]));
972 // Implemented by copying most of the MakeRelative code
974 public Uri MakeRelativeUri (Uri uri)
977 throw new ArgumentNullException ("uri");
978 if (Host != uri.Host || Scheme != uri.Scheme)
981 string result = String.Empty;
982 if (this.path != uri.path){
983 string [] segments = this.Segments;
984 string [] segments2 = uri.Segments;
987 int max = Math.Min (segments.Length, segments2.Length);
989 if (segments [k] != segments2 [k])
992 for (int i = k; i < segments.Length && segments [i].EndsWith ("/", StringComparison.Ordinal); i++)
994 for (int i = k; i < segments2.Length; i++)
995 result += segments2 [i];
997 if (result == string.Empty)
1000 uri.AppendQueryAndFragment (ref result);
1002 return new Uri (result, UriKind.Relative);
1005 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1006 public string MakeRelative (Uri toUri)
1008 if ((this.Scheme != toUri.Scheme) ||
1009 (this.Authority != toUri.Authority))
1010 return toUri.ToString ();
1012 string result = String.Empty;
1013 if (this.path != toUri.path){
1014 string [] segments = this.Segments;
1015 string [] segments2 = toUri.Segments;
1017 int max = Math.Min (segments.Length, segments2.Length);
1018 for (; k < max; k++)
1019 if (segments [k] != segments2 [k])
1022 for (int i = k + 1; i < segments.Length; i++)
1024 for (int i = k; i < segments2.Length; i++)
1025 result += segments2 [i];
1028 // Important: MakeRelative does not append fragment or query.
1033 void AppendQueryAndFragment (ref string result)
1035 if (query.Length > 0) {
1036 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true, false) : Unescape (query, false);
1039 if (fragment.Length > 0)
1040 result += Unescape (fragment, true, false);
1043 public override string ToString ()
1045 if (cachedToString != null)
1046 return cachedToString;
1048 if (isAbsoluteUri) {
1049 if (Parser is DefaultUriParser)
1050 cachedToString = Parser.GetComponentsHelper (this, UriComponents.AbsoluteUri, UriHelper.ToStringUnescape);
1052 cachedToString = Parser.GetComponents (this, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
1054 cachedToString = UriHelper.FormatRelative (source, scheme, UriHelper.ToStringUnescape);
1056 return cachedToString;
1059 protected void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
1061 if (this.isAbsoluteUri) {
1062 serializationInfo.AddValue ("AbsoluteUri", this.AbsoluteUri);
1064 serializationInfo.AddValue ("AbsoluteUri", String.Empty);
1065 serializationInfo.AddValue ("RelativeUri", this.OriginalString);
1069 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1071 GetObjectData (info, context);
1078 protected virtual void Escape ()
1080 path = EscapeString (path);
1084 protected static string EscapeString (string str)
1086 return EscapeString (str, Uri.EscapeCommonHexBrackets);
1089 private const string EscapeCommon = "<>%\"{}|\\^`";
1090 private const string EscapeReserved = ";/?:@&=+$,";
1091 private const string EscapeFragment = "#";
1092 private const string EscapeBrackets = "[]";
1094 private const string EscapeNews = EscapeCommon + EscapeBrackets + "?";
1095 private const string EscapeCommonHex = EscapeCommon + EscapeFragment;
1096 private const string EscapeCommonBrackets = EscapeCommon + EscapeBrackets;
1097 internal const string EscapeCommonHexBrackets = EscapeCommon + EscapeFragment + EscapeBrackets;
1098 internal const string EscapeCommonHexBracketsQuery = EscapeCommonHexBrackets + "?";
1100 internal static string EscapeString (string str, string escape)
1102 return EscapeString (str, escape, true);
1105 internal static string EscapeString (string str, string escape, bool nonAsciiEscape)
1107 if (String.IsNullOrEmpty (str))
1108 return String.Empty;
1110 StringBuilder s = new StringBuilder ();
1111 int len = str.Length;
1112 for (int i = 0; i < len; i++) {
1113 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1114 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1115 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1116 // space = <US-ASCII coded character 20 hexadecimal>
1117 // delims = "<" | ">" | "#" | "%" | <">
1118 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1120 // check for escape code already placed in str,
1121 // i.e. for encoding that follows the pattern
1122 // "%hexhex" in a string, where "hex" is a digit from 0-9
1123 // or a letter from A-F (case-insensitive).
1124 if (IsHexEncoding (str,i)) {
1125 // if ,yes , copy it as is
1126 s.Append (str.Substring (i, 3));
1132 bool outside_limited_ascii = ((c <= 0x20) || (c >= 0x7f));
1133 bool needs_escape = (escape.IndexOf (c) != -1);
1134 if (nonAsciiEscape && outside_limited_ascii) {
1135 byte [] data = Encoding.UTF8.GetBytes (new char [] { c });
1136 int length = data.Length;
1137 for (int j = 0; j < length; j++) {
1138 c = (char) data [j];
1139 if (needs_escape || nonAsciiEscape)
1140 s.Append (HexEscape (c));
1144 } else if (needs_escape) {
1145 s.Append (HexEscape (c));
1151 return s.ToString ();
1154 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1155 // can avoid the "absolute uri" constraints of the .ctor() by
1156 // overriding with custom code.
1157 [Obsolete("The method has been deprecated. It is not used by the system.")]
1158 protected virtual void Parse ()
1162 private void ParseUri (UriKind kind)
1164 Parse (kind, source);
1166 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1167 // host name present (but not an IPv6 address)
1168 host = host.ToLower (CultureInfo.InvariantCulture);
1173 protected virtual string Unescape (string path)
1175 var formatFlags = UriHelper.FormatFlags.NoSlashReplace | UriHelper.FormatFlags.NoReduce;
1176 return UriHelper.FormatAbsolute (path, scheme, UriComponents.Path, UriFormat.Unescaped, formatFlags);
1179 internal static string Unescape (string str, bool excludeSpecial)
1181 return Unescape (str, excludeSpecial, excludeSpecial);
1184 internal static string Unescape (string str, bool excludeSpecial, bool excludeBackslash)
1186 if (String.IsNullOrEmpty (str))
1187 return String.Empty;
1189 StringBuilder s = new StringBuilder ();
1190 int len = str.Length;
1191 for (int i = 0; i < len; i++) {
1195 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1196 if (excludeSpecial && x == '#')
1198 else if (excludeSpecial && x == '%')
1200 else if (excludeSpecial && x == '?')
1202 else if (excludeBackslash && x == '\\')
1206 if (surrogate != char.MinValue)
1207 s.Append (surrogate);
1213 return s.ToString ();
1219 private void ParseAsWindowsUNC (string uriString)
1221 scheme = UriSchemeFile;
1223 fragment = String.Empty;
1224 query = String.Empty;
1227 uriString = uriString.TrimStart (new char [] {'\\'});
1228 int pos = uriString.IndexOf ('\\');
1230 path = uriString.Substring (pos);
1231 host = uriString.Substring (0, pos);
1232 } else { // "\\\\server"
1234 path = String.Empty;
1236 path = path.Replace ("\\", "/");
1240 // Returns null on success, string with error on failure
1242 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1244 if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1245 return "Relative file path is not allowed.";
1246 scheme = UriSchemeFile;
1247 host = String.Empty;
1249 path = uriString.Replace ("\\", "/");
1250 fragment = String.Empty;
1251 query = String.Empty;
1256 private void ParseAsUnixAbsoluteFilePath (string uriString)
1258 scheme = UriSchemeFile;
1260 fragment = String.Empty;
1261 query = String.Empty;
1262 host = String.Empty;
1265 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1266 uriString = uriString.TrimStart (new char [] {'/'});
1267 // Now we don't regard //foo/bar as "foo" host.
1269 int pos = uriString.IndexOf ('/');
1271 path = '/' + uriString.Substring (pos + 1);
1272 host = uriString.Substring (0, pos);
1273 } else { // "///server"
1275 path = String.Empty;
1278 path = '/' + uriString;
1285 // This parse method will throw exceptions on failure
1287 private void Parse (UriKind kind, string uriString)
1289 if (uriString == null)
1290 throw new ArgumentNullException ("uriString");
1292 string s = ParseNoExceptions (kind, uriString);
1294 throw new UriFormatException (s);
1297 private bool SupportsQuery ()
1299 return UriHelper.SupportsQuery (scheme);
1303 private string ParseNoExceptions (UriKind kind, string uriString)
1305 UriElements elements;
1307 if (!UriParseComponents.TryParseComponents (source, kind, out elements, out error))
1310 scheme = elements.scheme;
1311 var parser = UriParser.GetParser (scheme);
1312 if (parser != null && !(parser is DefaultUriParser)) {
1313 userinfo = Parser.GetComponents (this, UriComponents.UserInfo, UriFormat.UriEscaped);
1314 host = Parser.GetComponents (this, UriComponents.Host, UriFormat.UriEscaped);
1316 var portStr = Parser.GetComponents (this, UriComponents.StrongPort, UriFormat.UriEscaped);
1317 if (!string.IsNullOrEmpty (portStr))
1318 port = int.Parse (portStr);
1320 path = Parser.GetComponents (this, UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
1321 query = Parser.GetComponents (this, UriComponents.Query, UriFormat.UriEscaped);
1322 fragment = Parser.GetComponents (this, UriComponents.StrongPort, UriFormat.UriEscaped);
1327 var formatFlags = UriHelper.FormatFlags.None;
1328 if (UriHelper.HasCharactersToNormalize (uriString))
1329 formatFlags |= UriHelper.FormatFlags.HasUriCharactersToNormalize;
1332 formatFlags |= UriHelper.FormatFlags.UserEscaped;
1334 if (elements.host != null)
1335 formatFlags |= UriHelper.FormatFlags.HasHost;
1337 userinfo = elements.user;
1339 if (elements.host != null) {
1340 host = UriHelper.FormatAbsolute (elements.host, scheme,
1341 UriComponents.Host, UriFormat.UriEscaped, formatFlags);
1344 port = elements.port;
1347 port = GetDefaultPort (scheme);
1349 if (elements.path != null) {
1350 path = UriHelper.FormatAbsolute (elements.path, scheme,
1351 UriComponents.Path, UriFormat.UriEscaped, formatFlags);
1352 if (elements.delimiter == SchemeDelimiter && string.IsNullOrEmpty (path))
1356 if (elements.query != null) {
1357 query = "?" + UriHelper.FormatAbsolute (elements.query, scheme,
1358 UriComponents.Query, UriFormat.UriEscaped, formatFlags);
1361 if (elements.fragment != null) {
1362 fragment = "#" + UriHelper.FormatAbsolute (elements.fragment, scheme,
1363 UriComponents.Fragment, UriFormat.UriEscaped, formatFlags);
1366 isAbsoluteUri = elements.isAbsoluteUri;
1367 isUnc = elements.isUnc;
1368 scope_id = elements.scopeId;
1373 private static string TryGetKnownUriSchemeInstance (string scheme)
1375 foreach (string knownScheme in knownUriSchemes) {
1376 if (knownScheme == scheme)
1383 private static bool CompactEscaped (string scheme)
1385 if (scheme == null || scheme.Length < 4)
1388 char first = scheme [0];
1390 return scheme == "http" || scheme == "https";
1391 } else if (first == 'f' && scheme == "file"){
1393 } else if (first == 'n')
1394 return scheme == "net.pipe" || scheme == "net.tcp";
1399 // replace '\', %5C ('\') and %2f ('/') into '/'
1400 // replace %2e ('.') into '.'
1401 private static string NormalizePath (string path)
1403 StringBuilder res = new StringBuilder ();
1404 for (int i = 0; i < path.Length; i++) {
1411 if (i < path.Length - 2) {
1412 char c1 = path [i + 1];
1413 char c2 = Char.ToUpper (path [i + 2]);
1414 if ((c1 == '2') && (c2 == 'E')) {
1417 } else if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1426 return res.ToString ();
1429 // This is called "compacting" in the MSDN documentation
1430 private static string Reduce (string path, bool compact_escaped)
1432 // quick out, allocation-free, for a common case
1436 if (compact_escaped && (path.IndexOf ('%') != -1)) {
1437 // replace '\', %2f, %5c with '/' and replace %2e with '.'
1438 // other escaped values seems to survive this step
1439 path = NormalizePath (path);
1441 // (always) replace '\' with '/'
1442 path = path.Replace ('\\', '/');
1445 List<string> result = new List<string> ();
1448 for (int startpos = 0; startpos < path.Length; ) {
1449 int endpos = path.IndexOf ('/', startpos);
1451 endpos = path.Length;
1452 string current = path.Substring (startpos, endpos-startpos);
1453 startpos = endpos + 1;
1454 if ((begin && current.Length == 0) || current == "." ) {
1460 if (current == "..") {
1461 int resultCount = result.Count;
1462 // in 2.0 profile, skip leading ".." parts
1463 if (resultCount == 0) {
1467 result.RemoveAt (resultCount - 1);
1471 result.Add (current);
1474 if (result.Count == 0)
1477 StringBuilder res = new StringBuilder ();
1479 if (path [0] == '/')
1483 foreach (string part in result) {
1492 if (path [path.Length - 1] == '/')
1495 return res.ToString ();
1498 // A variant of HexUnescape() which can decode multi-byte escaped
1499 // sequences such as (e.g.) %E3%81%8B into a single character
1500 internal static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1503 return HexUnescapeMultiByte (pattern, ref index, out surrogate, out invalidEscape);
1506 internal static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate, out bool invalidEscape)
1508 surrogate = char.MinValue;
1509 invalidEscape = false;
1511 if (pattern == null)
1512 throw new ArgumentException ("pattern");
1514 if (index < 0 || index >= pattern.Length)
1515 throw new ArgumentOutOfRangeException ("index");
1517 if (!IsHexEncoding (pattern, index))
1518 return pattern [index++];
1520 int orig_index = index++;
1521 int msb = FromHex (pattern [index++]);
1522 int lsb = FromHex (pattern [index++]);
1524 // We might be dealing with a multi-byte character:
1525 // The number of ones at the top-end of the first byte will tell us
1526 // how many bytes will make up this character.
1529 while ((msb_copy & 0x8) == 0x8) {
1534 // We might be dealing with a single-byte character:
1535 // If there was only 0 or 1 leading ones then we're not dealing
1536 // with a multi-byte character.
1537 if (num_bytes <= 1) {
1538 var c = (char) ((msb << 4) | lsb);
1539 invalidEscape = c > 0x7F;
1543 // Now that we know how many bytes *should* follow, we'll check them
1544 // to ensure we are dealing with a valid multi-byte character.
1545 byte [] chars = new byte [num_bytes];
1546 bool all_invalid = false;
1547 chars[0] = (byte) ((msb << 4) | lsb);
1549 for (int i = 1; i < num_bytes; i++) {
1550 if (!IsHexEncoding (pattern, index++)) {
1555 // All following bytes must be in the form 10xxxxxx
1556 int cur_msb = FromHex (pattern [index++]);
1557 if ((cur_msb & 0xc) != 0x8) {
1562 int cur_lsb = FromHex (pattern [index++]);
1563 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1566 // If what looked like a multi-byte character is invalid, then we'll
1567 // just return the first byte as a single byte character.
1569 invalidEscape = true;
1570 index = orig_index + 3;
1571 return (char) chars[0];
1574 // Otherwise, we're dealing with a valid multi-byte character.
1575 // We need to ignore the leading ones from the first byte:
1576 byte mask = (byte) 0xFF;
1577 mask >>= (num_bytes + 1);
1578 int result = chars[0] & mask;
1580 // The result will now be built up from the following bytes.
1581 for (int i = 1; i < num_bytes; i++) {
1582 // Ignore upper two bits
1584 result |= (chars[i] & 0x3F);
1587 if (result <= 0xFFFF) {
1588 return (char) result;
1590 // We need to handle this as a UTF16 surrogate (i.e. return
1593 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1594 return (char) ((result >> 10) | 0xD800);
1598 private struct UriScheme
1600 public string scheme;
1601 public string delimiter;
1602 public int defaultPort;
1604 public UriScheme (string s, string d, int p)
1612 static UriScheme [] schemes = new UriScheme [] {
1613 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1614 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1615 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1616 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1617 new UriScheme (UriSchemeMailto, ":", 25),
1618 new UriScheme (UriSchemeNews, ":", 119),
1619 new UriScheme (UriSchemeUuid, ":", -1),
1620 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1621 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1624 internal static string GetSchemeDelimiter (string scheme)
1626 for (int i = 0; i < schemes.Length; i++)
1627 if (schemes [i].scheme == scheme)
1628 return schemes [i].delimiter;
1629 return Uri.SchemeDelimiter;
1632 internal static int GetDefaultPort (string scheme)
1634 UriParser parser = UriParser.GetParser (scheme);
1637 return parser.DefaultPort;
1640 private string GetOpaqueWiseSchemeDelimiter ()
1642 return GetSchemeDelimiter (scheme);
1646 protected virtual bool IsBadFileSystemCharacter (char character)
1648 // It does not always overlap with InvalidPathChars.
1649 int chInt = (int) character;
1650 if (chInt < 32 || (chInt < 64 && chInt > 57))
1669 protected static bool IsExcludedCharacter (char character)
1671 if (character <= 32 || character >= 127)
1674 if (character == '"' || character == '#' || character == '%' || character == '<' ||
1675 character == '>' || character == '[' || character == '\\' || character == ']' ||
1676 character == '^' || character == '`' || character == '{' || character == '|' ||
1682 internal static bool MaybeUri (string s)
1684 int p = s.IndexOf (':');
1691 return IsPredefinedScheme (s.Substring (0, p));
1695 // Using a simple block of if's is twice as slow as the compiler generated
1696 // switch statement. But using this tuned code is faster than the
1697 // compiler generated code, with a million loops on x86-64:
1699 // With "http": .10 vs .51 (first check)
1700 // with "https": .16 vs .51 (second check)
1701 // with "foo": .22 vs .31 (never found)
1702 // with "mailto": .12 vs .51 (last check)
1705 private static bool IsPredefinedScheme (string scheme)
1707 if (scheme == null || scheme.Length < 3)
1710 char c = scheme [0];
1712 return (scheme == "http" || scheme == "https");
1714 return (scheme == "file" || scheme == "ftp");
1719 return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
1720 if (scheme == "nntp")
1724 if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
1731 protected virtual bool IsReservedCharacter (char character)
1733 if (character == '$' || character == '&' || character == '+' || character == ',' ||
1734 character == '/' || character == ':' || character == ';' || character == '=' ||
1741 private UriParser parser;
1743 private UriParser Parser {
1745 if (parser == null) {
1746 parser = UriParser.GetParser (scheme);
1747 // no specific parser ? then use a default one
1749 parser = new DefaultUriParser ("*");
1753 set { parser = value; }
1756 public string GetComponents (UriComponents components, UriFormat format)
1758 if ((components & UriComponents.SerializationInfoString) == 0)
1759 EnsureAbsoluteUri ();
1761 return Parser.GetComponents (this, components, format);
1764 public bool IsBaseOf (Uri uri)
1767 throw new ArgumentNullException ("uri");
1768 return Parser.IsBaseOf (this, uri);
1771 public bool IsWellFormedOriginalString ()
1773 // funny, but it does not use the Parser's IsWellFormedOriginalString().
1774 // Also, it seems we need to *not* escape hex.
1775 return EscapeString (OriginalString, EscapeCommonBrackets) == OriginalString;
1780 private const int MaxUriLength = 32766;
1782 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1784 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1785 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1786 throw new ArgumentException ("comparisonType", msg);
1789 if ((uri1 == null) && (uri2 == null))
1796 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1797 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1798 return String.Compare (s1, s2, comparisonType);
1802 // The rules for EscapeDataString
1804 static bool NeedToEscapeDataChar (char b)
1806 if ((b >= 'A' && b <= 'Z') ||
1807 (b >= 'a' && b <= 'z') ||
1808 (b >= '0' && b <= '9'))
1823 public static string EscapeDataString (string stringToEscape)
1825 if (stringToEscape == null)
1826 throw new ArgumentNullException ("stringToEscape");
1828 if (stringToEscape.Length > MaxUriLength) {
1829 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
1832 bool escape = false;
1833 foreach (char c in stringToEscape){
1834 if (NeedToEscapeDataChar (c)){
1840 return stringToEscape;
1843 StringBuilder sb = new StringBuilder ();
1844 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1845 foreach (byte b in bytes){
1846 if (NeedToEscapeDataChar ((char) b))
1847 sb.Append (HexEscape ((char) b));
1849 sb.Append ((char) b);
1851 return sb.ToString ();
1855 // The rules for EscapeUriString
1857 static bool NeedToEscapeUriChar (char b)
1859 if ((b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '&' && b <= ';'))
1880 public static string EscapeUriString (string stringToEscape)
1882 if (stringToEscape == null)
1883 throw new ArgumentNullException ("stringToEscape");
1885 if (stringToEscape.Length > MaxUriLength) {
1886 throw new UriFormatException (string.Format ("Uri is longer than the maximum {0} characters.", MaxUriLength));
1889 bool escape = false;
1890 foreach (char c in stringToEscape){
1891 if (NeedToEscapeUriChar (c)){
1897 return stringToEscape;
1899 StringBuilder sb = new StringBuilder ();
1900 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
1901 foreach (byte b in bytes){
1902 if (NeedToEscapeUriChar ((char) b))
1903 sb.Append (HexEscape ((char) b));
1905 sb.Append ((char) b);
1907 return sb.ToString ();
1910 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
1912 if (uriString == null)
1916 if (Uri.TryCreate (uriString, uriKind, out uri))
1917 return uri.IsWellFormedOriginalString ();
1921 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
1925 Uri r = new Uri (uriString, uriKind, out success);
1934 // [MonoTODO ("rework code to avoid exception catching")]
1935 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
1938 if (relativeUri == null)
1942 Uri relative = new Uri (relativeUri, UriKind.RelativeOrAbsolute);
1943 if ((baseUri != null) && baseUri.IsAbsoluteUri) {
1944 // FIXME: this should call UriParser.Resolve
1945 result = new Uri (baseUri, relative);
1946 } else if (relative.IsAbsoluteUri) {
1947 // special case - see unit tests
1950 return (result != null);
1951 } catch (UriFormatException) {
1956 //[MonoTODO ("rework code to avoid exception catching")]
1957 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
1960 if ((baseUri == null) || !baseUri.IsAbsoluteUri)
1962 if (relativeUri == null)
1965 // FIXME: this should call UriParser.Resolve
1966 result = new Uri (baseUri, relativeUri.OriginalString);
1968 } catch (UriFormatException) {
1973 public static string UnescapeDataString (string stringToUnescape)
1975 return UnescapeDataString (stringToUnescape, false);
1978 internal static string UnescapeDataString (string stringToUnescape, bool safe)
1980 if (stringToUnescape == null)
1981 throw new ArgumentNullException ("stringToUnescape");
1983 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
1984 return stringToUnescape;
1986 StringBuilder output = new StringBuilder ();
1987 long len = stringToUnescape.Length;
1988 MemoryStream bytes = new MemoryStream ();
1991 for (int i = 0; i < len; i++) {
1992 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
1993 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
1994 if (bytes.Length > 0) {
1995 output.Append (GetChars (bytes, Encoding.UTF8));
1996 bytes.SetLength (0);
1999 xchar = GetChar (stringToUnescape, i + 2, 4, safe);
2001 output.Append ((char) xchar);
2005 output.Append ('%');
2008 else if ((xchar = GetChar (stringToUnescape, i + 1, 2, safe)) != -1) {
2009 bytes.WriteByte ((byte) xchar);
2013 output.Append ('%');
2018 if (bytes.Length > 0) {
2019 output.Append (GetChars (bytes, Encoding.UTF8));
2020 bytes.SetLength (0);
2023 output.Append (stringToUnescape [i]);
2026 if (bytes.Length > 0) {
2027 output.Append (GetChars (bytes, Encoding.UTF8));
2031 return output.ToString ();
2034 private static int GetInt (byte b)
2037 if (c >= '0' && c <= '9')
2040 if (c >= 'a' && c <= 'f')
2041 return c - 'a' + 10;
2043 if (c >= 'A' && c <= 'F')
2044 return c - 'A' + 10;
2049 private static int GetChar (string str, int offset, int length, bool safe)
2052 int end = length + offset;
2053 for (int i = offset; i < end; i++) {
2058 int current = GetInt ((byte) c);
2061 val = (val << 4) + current;
2067 switch ((char) val) {
2074 case '&': // not documented
2081 private static char [] GetChars (MemoryStream b, Encoding e)
2083 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2087 private void EnsureAbsoluteUri ()
2090 throw new InvalidOperationException ("This operation is not supported for a relative URI.");