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.Globalization;
51 // Disable warnings on Obsolete methods being used
53 #pragma warning disable 612
59 [TypeConverter (typeof (UriTypeConverter))]
60 public class Uri : ISerializable {
62 public class Uri : MarshalByRefObject, ISerializable {
65 // o scheme excludes the scheme delimiter
66 // o port is -1 to indicate no port is defined
67 // o path is empty or starts with / when scheme delimiter == "://"
68 // o query is empty or starts with ? char, escaped.
69 // o fragment is empty or starts with # char, unescaped.
70 // o all class variables are in escaped format when they are escapable,
71 // except cachedToString.
72 // o UNC is supported, as starts with "\\" for windows,
75 private bool isUnixFilePath;
76 private string source;
77 private string scheme = String.Empty;
78 private string host = String.Empty;
79 private int port = -1;
80 private string path = String.Empty;
81 private string query = String.Empty;
82 private string fragment = String.Empty;
83 private string userinfo = String.Empty;
85 private bool isOpaquePart;
86 private bool isAbsoluteUri = true;
88 private string [] segments;
90 private bool userEscaped;
91 private string cachedAbsoluteUri;
92 private string cachedToString;
93 private string cachedLocalPath;
94 private int cachedHashCode;
96 private static readonly string hexUpperChars = "0123456789ABCDEF";
100 public static readonly string SchemeDelimiter = "://";
101 public static readonly string UriSchemeFile = "file";
102 public static readonly string UriSchemeFtp = "ftp";
103 public static readonly string UriSchemeGopher = "gopher";
104 public static readonly string UriSchemeHttp = "http";
105 public static readonly string UriSchemeHttps = "https";
106 public static readonly string UriSchemeMailto = "mailto";
107 public static readonly string UriSchemeNews = "news";
108 public static readonly string UriSchemeNntp = "nntp";
110 public static readonly string UriSchemeNetPipe = "net.pipe";
111 public static readonly string UriSchemeNetTcp = "net.tcp";
116 #if NET_2_1 && !MONOTOUCH
117 public Uri (string uriString) : this (uriString, UriKind.Absolute)
121 public Uri (string uriString) : this (uriString, false)
125 protected Uri (SerializationInfo serializationInfo,
126 StreamingContext streamingContext) :
127 this (serializationInfo.GetString ("AbsoluteUri"), true)
132 public Uri (string uriString, UriKind uriKind)
138 case UriKind.Absolute:
140 throw new UriFormatException("Invalid URI: The format of the URI could not be "
143 case UriKind.Relative:
145 throw new UriFormatException("Invalid URI: The format of the URI could not be "
146 + "determined because the parameter 'uriString' represents an absolute URI.");
148 case UriKind.RelativeOrAbsolute:
151 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
152 throw new ArgumentException (msg);
157 // An exception-less constructor, returns success
158 // condition on the out parameter `success'.
160 Uri (string uriString, UriKind uriKind, out bool success)
162 if (uriString == null) {
167 if (uriKind != UriKind.RelativeOrAbsolute &&
168 uriKind != UriKind.Absolute &&
169 uriKind != UriKind.Relative) {
170 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
171 throw new ArgumentException (msg);
175 if (ParseNoExceptions (uriKind, uriString) != null)
181 case UriKind.Absolute:
185 case UriKind.Relative:
189 case UriKind.RelativeOrAbsolute:
198 public Uri (Uri baseUri, Uri relativeUri)
200 Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
201 // FIXME: this should call UriParser.Resolve
204 // note: doc says that dontEscape is always false but tests show otherwise
206 public Uri (string uriString, bool dontEscape)
208 userEscaped = dontEscape;
210 ParseUri (UriKind.Absolute);
212 throw new UriFormatException("Invalid URI: The format of the URI could not be "
213 + "determined: " + uriString);
216 public Uri (string uriString, bool dontEscape)
218 userEscaped = dontEscape;
222 throw new UriFormatException("Invalid URI: The format of the URI could not be "
227 public Uri (Uri baseUri, string relativeUri)
229 Merge (baseUri, relativeUri);
230 // FIXME: this should call UriParser.Resolve
234 [Obsolete ("dontEscape is always false")]
236 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
238 userEscaped = dontEscape;
239 Merge (baseUri, relativeUri);
242 private void Merge (Uri baseUri, string relativeUri)
246 throw new ArgumentNullException ("baseUri");
247 if (!baseUri.IsAbsoluteUri)
248 throw new ArgumentOutOfRangeException ("baseUri");
249 if (relativeUri == null)
250 relativeUri = String.Empty;
253 throw new NullReferenceException ("baseUri");
255 // See RFC 2396 Par 5.2 and Appendix C
257 // Check Windows UNC (for // it is scheme/host separator)
258 if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
259 source = relativeUri;
261 ParseUri (UriKind.Absolute);
268 int pos = relativeUri.IndexOf (':');
271 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
273 // pos2 < 0 ... e.g. mailto
274 // pos2 > pos ... to block ':' in query part
275 if (pos2 > pos || pos2 < 0) {
276 // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
277 // 1) when the URI scheme in the
278 // relative path is different from that
279 // of the baseUri, or
280 // 2) the URI scheme is non-standard
281 // ones (non-standard URIs are always
282 // treated as absolute here), or
283 // 3) the relative URI path is absolute.
284 if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
285 !IsPredefinedScheme (baseUri.Scheme) ||
286 relativeUri.Length > pos + 1 &&
287 relativeUri [pos + 1] == '/') {
288 source = relativeUri;
290 ParseUri (UriKind.Absolute);
297 relativeUri = relativeUri.Substring (pos + 1);
301 this.scheme = baseUri.scheme;
302 this.host = baseUri.host;
303 this.port = baseUri.port;
304 this.userinfo = baseUri.userinfo;
305 this.isUnc = baseUri.isUnc;
306 this.isUnixFilePath = baseUri.isUnixFilePath;
307 this.isOpaquePart = baseUri.isOpaquePart;
309 if (relativeUri == String.Empty) {
310 this.path = baseUri.path;
311 this.query = baseUri.query;
312 this.fragment = baseUri.fragment;
317 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
318 pos = relativeUri.IndexOf ('#');
321 fragment = relativeUri.Substring (pos);
323 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
324 relativeUri = relativeUri.Substring (0, pos);
328 pos = relativeUri.IndexOf ('?');
330 query = relativeUri.Substring (pos);
332 query = EscapeString (query);
333 relativeUri = relativeUri.Substring (0, pos);
336 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
337 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
338 source = scheme + ':' + relativeUri;
340 ParseUri (UriKind.Absolute);
348 path = EscapeString (path);
355 if (relativeUri.Length > 0 || query.Length > 0) {
356 pos = path.LastIndexOf ('/');
358 path = path.Substring (0, pos + 1);
361 if(relativeUri.Length == 0)
370 pos = path.IndexOf ("./", startIndex);
374 path = path.Remove (0, 2);
375 else if (path [pos - 1] != '.')
376 path = path.Remove (pos, 2);
378 startIndex = pos + 1;
382 if (path.Length > 1 &&
383 path [path.Length - 1] == '.' &&
384 path [path.Length - 2] == '/')
385 path = path.Remove (path.Length - 1, 1);
390 pos = path.IndexOf ("/../", startIndex);
397 int pos2 = path.LastIndexOf ('/', pos - 1);
399 startIndex = pos + 1;
401 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
402 path = path.Remove (pos2 + 1, pos - pos2 + 3);
404 startIndex = pos + 1;
409 if (path.Length > 3 && path.EndsWith ("/..")) {
410 pos = path.LastIndexOf ('/', path.Length - 4);
412 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
413 path = path.Remove (pos + 1, path.Length - pos - 1);
417 path = EscapeString (path);
422 public string AbsolutePath {
425 EnsureAbsoluteUri ();
429 // faster (mailto) and special (file) cases
432 if (path.Length == 0) {
433 string start = Scheme + SchemeDelimiter;
434 if (path.StartsWith (start))
447 public string AbsoluteUri {
449 EnsureAbsoluteUri ();
450 if (cachedAbsoluteUri == null) {
451 cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
452 if (query.Length > 0)
453 cachedAbsoluteUri += query;
454 if (fragment.Length > 0)
455 cachedAbsoluteUri += fragment;
457 return cachedAbsoluteUri;
461 public string Authority {
463 EnsureAbsoluteUri ();
464 return (GetDefaultPort (Scheme) == port)
465 ? host : host + ":" + port;
469 public string Fragment {
471 EnsureAbsoluteUri ();
478 EnsureAbsoluteUri ();
482 #if !NET_2_1 || MONOTOUCH
483 public UriHostNameType HostNameType {
485 EnsureAbsoluteUri ();
486 UriHostNameType ret = CheckHostName (Host);
487 if (ret != UriHostNameType.Unknown)
492 return UriHostNameType.Basic;
494 return (IsFile) ? UriHostNameType.Basic : ret;
497 // looks it always returns Basic...
498 return UriHostNameType.Basic; //.Unknown;
505 public bool IsDefaultPort {
507 EnsureAbsoluteUri ();
508 return GetDefaultPort (Scheme) == port;
514 EnsureAbsoluteUri ();
515 return (Scheme == UriSchemeFile);
519 #if !NET_2_1 || MONOTOUCH
520 public bool IsLoopback {
522 EnsureAbsoluteUri ();
524 if (Host.Length == 0) {
532 if (host == "loopback" || host == "localhost")
536 if (IPAddress.TryParse (host, out result))
537 if (IPAddress.Loopback.Equals (result))
541 if (IPv6Address.TryParse (host, out result6)){
542 if (IPv6Address.IsLoopback (result6))
553 // rule: This should be true only if
554 // - uri string starts from "\\", or
555 // - uri string starts from "//" (Samba way)
557 EnsureAbsoluteUri ();
562 public string LocalPath {
564 EnsureAbsoluteUri ();
565 if (cachedLocalPath != null)
566 return cachedLocalPath;
570 bool windows = (path.Length > 3 && path [1] == ':' &&
571 (path [2] == '\\' || path [2] == '/'));
574 string p = Unescape (path);
575 bool replace = windows;
577 replace |= (System.IO.Path.DirectorySeparatorChar == '\\');
580 cachedLocalPath = p.Replace ('/', '\\');
584 // support *nix and W32 styles
585 if (path.Length > 1 && path [1] == ':')
586 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
588 // LAMESPEC: ok, now we cannot determine
589 // if such URI like "file://foo/bar" is
590 // Windows UNC or unix file path, so
591 // they should be handled differently.
592 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
594 if (path.Length > 0) {
596 if ((path.Length > 1) || (path[0] != '/')) {
597 h += path.Replace ('/', '\\');
600 h += path.Replace ('/', '\\');
603 cachedLocalPath = "\\\\" + Unescape (h);
605 cachedLocalPath = Unescape (path);
607 if (cachedLocalPath.Length == 0)
608 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
609 return cachedLocalPath;
613 public string PathAndQuery {
615 EnsureAbsoluteUri ();
622 EnsureAbsoluteUri ();
627 public string Query {
629 EnsureAbsoluteUri ();
634 public string Scheme {
636 EnsureAbsoluteUri ();
641 public string [] Segments {
643 EnsureAbsoluteUri ();
644 if (segments != null)
647 if (path.Length == 0) {
648 segments = new string [0];
652 string [] parts = path.Split ('/');
654 bool endSlash = path.EndsWith ("/");
655 if (parts.Length > 0 && endSlash) {
656 string [] newParts = new string [parts.Length - 1];
657 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
662 if (IsFile && path.Length > 1 && path [1] == ':') {
663 string [] newParts = new string [parts.Length + 1];
664 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
666 parts [0] = path.Substring (0, 2);
667 parts [1] = String.Empty;
671 int end = parts.Length;
673 if (i != end - 1 || endSlash)
681 public bool UserEscaped {
682 get { return userEscaped; }
685 public string UserInfo {
687 EnsureAbsoluteUri ();
693 [MonoTODO ("add support for IPv6 address")]
694 public string DnsSafeHost {
696 EnsureAbsoluteUri ();
697 return Unescape (Host);
701 public bool IsAbsoluteUri {
702 get { return isAbsoluteUri; }
705 // LAMESPEC: source field is supplied in such case that this
706 // property makes sense. For such case that source field is
707 // not supplied (i.e. .ctor(Uri, string), this property
708 // makes no sense. To avoid silly regression it just returns
709 // ToString() value now. See bug #78374.
715 string OriginalString {
716 get { return source != null ? source : ToString (); }
721 public static UriHostNameType CheckHostName (string name)
723 if (name == null || name.Length == 0)
724 return UriHostNameType.Unknown;
726 if (IsIPv4Address (name))
727 return UriHostNameType.IPv4;
729 if (IsDomainAddress (name))
730 return UriHostNameType.Dns;
733 if (IPv6Address.TryParse (name, out addr))
734 return UriHostNameType.IPv6;
736 return UriHostNameType.Unknown;
739 internal static bool IsIPv4Address (string name)
741 string [] captures = name.Split (new char [] {'.'});
742 if (captures.Length != 4)
745 for (int i = 0; i < 4; i++) {
748 length = captures [i].Length;
753 if (!UInt32.TryParse (captures [i], out number))
757 number = UInt32.Parse (captures [i]);
758 } catch (Exception) {
768 internal static bool IsDomainAddress (string name)
770 int len = name.Length;
773 for (int i = 0; i < len; i++) {
776 if (!Char.IsLetterOrDigit (c))
778 } else if (c == '.') {
780 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
792 [Obsolete("This method does nothing, it has been obsoleted")]
794 protected virtual void Canonicalize ()
797 // This is flagged in the Microsoft documentation as used
798 // internally, no longer in use, and Obsolete.
802 [MonoTODO ("Find out what this should do")]
806 protected virtual void CheckSecurity ()
812 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
813 public static bool CheckSchemeName (string schemeName)
815 if (schemeName == null || schemeName.Length == 0)
818 if (!IsAlpha (schemeName [0]))
821 int len = schemeName.Length;
822 for (int i = 1; i < len; i++) {
823 char c = schemeName [i];
824 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
831 private static bool IsAlpha (char c)
834 // as defined in rfc2234
835 // %x41-5A / %x61-7A (A-Z / a-z)
837 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
839 // Fx 1.x got this too large
840 return Char.IsLetter (c);
844 public override bool Equals (object comparant)
846 if (comparant == null)
849 Uri uri = comparant as Uri;
850 if ((object) uri == null) {
851 string s = comparant as String;
857 return InternalEquals (uri);
860 // Assumes: uri != null
861 bool InternalEquals (Uri uri)
864 if (this.isAbsoluteUri != uri.isAbsoluteUri)
866 if (!this.isAbsoluteUri)
867 return this.source == uri.source;
870 CultureInfo inv = CultureInfo.InvariantCulture;
871 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
872 && this.host.ToLower (inv) == uri.host.ToLower (inv)
873 && this.port == uri.port
875 && this.query == uri.query
877 // Note: MS.NET 1.x has bug - ignores query check altogether
878 && this.query.ToLower (inv) == uri.query.ToLower (inv)
880 && this.path == uri.path;
884 public static bool operator == (Uri u1, Uri u2)
886 return object.Equals(u1, u2);
889 public static bool operator != (Uri u1, Uri u2)
895 public override int GetHashCode ()
897 if (cachedHashCode == 0) {
898 CultureInfo inv = CultureInfo.InvariantCulture;
900 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
901 ^ host.ToLower (inv).GetHashCode ()
904 ^ query.GetHashCode ()
906 ^ query.ToLower (inv).GetHashCode ()
908 ^ path.GetHashCode ();
911 cachedHashCode = source.GetHashCode ();
914 return cachedHashCode;
917 public string GetLeftPart (UriPartial part)
919 EnsureAbsoluteUri ();
922 case UriPartial.Scheme :
923 return scheme + GetOpaqueWiseSchemeDelimiter ();
924 case UriPartial.Authority :
925 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
928 StringBuilder s = new StringBuilder ();
930 s.Append (GetOpaqueWiseSchemeDelimiter ());
931 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
932 s.Append ('/'); // win32 file
933 if (userinfo.Length > 0)
934 s.Append (userinfo).Append ('@');
936 defaultPort = GetDefaultPort (scheme);
937 if ((port != -1) && (port != defaultPort))
938 s.Append (':').Append (port);
939 return s.ToString ();
940 case UriPartial.Path :
941 StringBuilder sb = new StringBuilder ();
943 sb.Append (GetOpaqueWiseSchemeDelimiter ());
944 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
945 sb.Append ('/'); // win32 file
946 if (userinfo.Length > 0)
947 sb.Append (userinfo).Append ('@');
949 defaultPort = GetDefaultPort (scheme);
950 if ((port != -1) && (port != defaultPort))
951 sb.Append (':').Append (port);
953 if (path.Length > 0) {
961 sb.Append (Reduce (path));
968 return sb.ToString ();
973 public static int FromHex (char digit)
975 if ('0' <= digit && digit <= '9') {
976 return (int) (digit - '0');
979 if ('a' <= digit && digit <= 'f')
980 return (int) (digit - 'a' + 10);
982 if ('A' <= digit && digit <= 'F')
983 return (int) (digit - 'A' + 10);
985 throw new ArgumentException ("digit");
988 public static string HexEscape (char character)
990 if (character > 255) {
991 throw new ArgumentOutOfRangeException ("character");
994 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
995 + hexUpperChars [((character & 0x0f))];
998 public static char HexUnescape (string pattern, ref int index)
1000 if (pattern == null)
1001 throw new ArgumentException ("pattern");
1003 if (index < 0 || index >= pattern.Length)
1004 throw new ArgumentOutOfRangeException ("index");
1006 if (!IsHexEncoding (pattern, index))
1007 return pattern [index++];
1010 int msb = FromHex (pattern [index++]);
1011 int lsb = FromHex (pattern [index++]);
1012 return (char) ((msb << 4) | lsb);
1015 public static bool IsHexDigit (char digit)
1017 return (('0' <= digit && digit <= '9') ||
1018 ('a' <= digit && digit <= 'f') ||
1019 ('A' <= digit && digit <= 'F'));
1022 public static bool IsHexEncoding (string pattern, int index)
1024 if ((index + 3) > pattern.Length)
1027 return ((pattern [index++] == '%') &&
1028 IsHexDigit (pattern [index++]) &&
1029 IsHexDigit (pattern [index]));
1034 // Implemented by copying most of the MakeRelative code
1036 public Uri MakeRelativeUri (Uri uri)
1039 throw new ArgumentNullException ("uri");
1041 if (Host != uri.Host || Scheme != uri.Scheme)
1044 string result = String.Empty;
1045 if (this.path != uri.path){
1046 string [] segments = this.Segments;
1047 string [] segments2 = uri.Segments;
1050 int max = Math.Min (segments.Length, segments2.Length);
1051 for (; k < max; k++)
1052 if (segments [k] != segments2 [k])
1055 for (int i = k + 1; i < segments.Length; i++)
1057 for (int i = k; i < segments2.Length; i++)
1058 result += segments2 [i];
1061 uri.AppendQueryAndFragment (ref result);
1063 return new Uri (result, UriKind.Relative);
1066 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1068 public string MakeRelative (Uri toUri)
1070 if ((this.Scheme != toUri.Scheme) ||
1071 (this.Authority != toUri.Authority))
1072 return toUri.ToString ();
1074 string result = String.Empty;
1075 if (this.path != toUri.path){
1076 string [] segments = this.Segments;
1077 string [] segments2 = toUri.Segments;
1079 int max = Math.Min (segments.Length, segments2.Length);
1080 for (; k < max; k++)
1081 if (segments [k] != segments2 [k])
1084 for (int i = k + 1; i < segments.Length; i++)
1086 for (int i = k; i < segments2.Length; i++)
1087 result += segments2 [i];
1090 // Important: MakeRelative does not append fragment or query.
1095 void AppendQueryAndFragment (ref string result)
1097 if (query.Length > 0) {
1098 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), false) : Unescape (query, false);
1101 if (fragment.Length > 0)
1105 public override string ToString ()
1107 if (cachedToString != null)
1108 return cachedToString;
1111 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1113 // Everything is contained in path in this case.
1114 cachedToString = Unescape (path);
1117 AppendQueryAndFragment (ref cachedToString);
1118 return cachedToString;
1122 protected void GetObjectData (SerializationInfo info, StreamingContext context)
1124 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1128 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1130 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1139 protected virtual void Escape ()
1141 path = EscapeString (path);
1144 #if NET_2_1 && !MONOTOUCH
1145 static string EscapeString (string str)
1150 protected static string EscapeString (string str)
1153 return EscapeString (str, false, true, true);
1156 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
1159 return String.Empty;
1161 StringBuilder s = new StringBuilder ();
1162 int len = str.Length;
1163 for (int i = 0; i < len; i++) {
1164 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1165 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1166 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1167 // space = <US-ASCII coded character 20 hexadecimal>
1168 // delims = "<" | ">" | "#" | "%" | <">
1169 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1171 // check for escape code already placed in str,
1172 // i.e. for encoding that follows the pattern
1173 // "%hexhex" in a string, where "hex" is a digit from 0-9
1174 // or a letter from A-F (case-insensitive).
1175 if (IsHexEncoding (str,i)) {
1176 // if ,yes , copy it as is
1177 s.Append(str.Substring (i, 3));
1182 byte [] data = Encoding.UTF8.GetBytes (new char[] {str[i]});
1183 int length = data.Length;
1184 for (int j = 0; j < length; j++) {
1185 char c = (char) data [j];
1186 if ((c <= 0x20) || (c >= 0x7f) ||
1187 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
1188 (escapeHex && (c == '#')) ||
1189 (escapeBrackets && (c == '[' || c == ']')) ||
1190 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
1191 s.Append (HexEscape (c));
1198 return s.ToString ();
1201 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1202 // can avoid the "absolute uri" constraints of the .ctor() by
1203 // overriding with custom code.
1205 [Obsolete("The method has been deprecated. It is not used by the system.")]
1207 protected virtual void Parse ()
1210 ParseUri (UriKind.Absolute);
1214 private void ParseUri (UriKind kind)
1216 Parse (kind, source);
1221 host = EscapeString (host, false, true, false);
1222 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1223 // host name present (but not an IPv6 address)
1224 host = host.ToLower (CultureInfo.InvariantCulture);
1227 if (path.Length > 0) {
1228 path = EscapeString (path);
1232 #if NET_2_1 && !MONOTOUCH
1233 string Unescape (string str)
1238 protected virtual string Unescape (string str)
1241 return Unescape (str, false);
1244 internal static string Unescape (string str, bool excludeSpecial)
1247 return String.Empty;
1248 StringBuilder s = new StringBuilder ();
1249 int len = str.Length;
1250 for (int i = 0; i < len; i++) {
1254 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1255 if (excludeSpecial && x == '#')
1257 else if (excludeSpecial && x == '%')
1259 else if (excludeSpecial && x == '?')
1263 if (surrogate != char.MinValue)
1264 s.Append (surrogate);
1270 return s.ToString ();
1276 private void ParseAsWindowsUNC (string uriString)
1278 scheme = UriSchemeFile;
1280 fragment = String.Empty;
1281 query = String.Empty;
1284 uriString = uriString.TrimStart (new char [] {'\\'});
1285 int pos = uriString.IndexOf ('\\');
1287 path = uriString.Substring (pos);
1288 host = uriString.Substring (0, pos);
1289 } else { // "\\\\server"
1291 path = String.Empty;
1293 path = path.Replace ("\\", "/");
1297 // Returns null on success, string with error on failure
1299 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1301 if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1302 return "Relative file path is not allowed.";
1303 scheme = UriSchemeFile;
1304 host = String.Empty;
1306 path = uriString.Replace ("\\", "/");
1307 fragment = String.Empty;
1308 query = String.Empty;
1313 private void ParseAsUnixAbsoluteFilePath (string uriString)
1315 isUnixFilePath = true;
1316 scheme = UriSchemeFile;
1318 fragment = String.Empty;
1319 query = String.Empty;
1320 host = String.Empty;
1323 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1324 uriString = uriString.TrimStart (new char [] {'/'});
1325 // Now we don't regard //foo/bar as "foo" host.
1327 int pos = uriString.IndexOf ('/');
1329 path = '/' + uriString.Substring (pos + 1);
1330 host = uriString.Substring (0, pos);
1331 } else { // "///server"
1333 path = String.Empty;
1336 path = '/' + uriString;
1343 // This parse method will throw exceptions on failure
1345 private void Parse (UriKind kind, string uriString)
1347 if (uriString == null)
1348 throw new ArgumentNullException ("uriString");
1350 string s = ParseNoExceptions (kind, uriString);
1352 throw new UriFormatException (s);
1356 // This parse method will not throw exceptions on failure
1358 // Returns null on success, or a description of the error in the parsing
1360 private string ParseNoExceptions (UriKind kind, string uriString)
1365 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1369 uriString = uriString.Trim();
1370 int len = uriString.Length;
1373 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1374 isAbsoluteUri = false;
1379 if (len <= 1 && (kind == UriKind.Absolute))
1380 return "Absolute URI is too short";
1385 // Identify Windows path, unix path, or standard URI.
1386 if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1388 ParseAsUnixAbsoluteFilePath (uriString);
1389 #if NET_2_1 && !MONOTOUCH
1390 isAbsoluteUri = false;
1392 if (kind == UriKind.Relative)
1393 isAbsoluteUri = false;
1396 } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\') {
1398 ParseAsWindowsUNC (uriString);
1403 pos = uriString.IndexOf (':');
1405 return "Invalid URI: The format of the URI could not be determined.";
1406 } else if (pos < 0) {
1408 isAbsoluteUri = false;
1411 } else if (pos == 1) {
1412 if (!IsAlpha (uriString [0]))
1413 return "URI scheme must start with a letter.";
1414 // This means 'a:' == windows full path.
1415 string msg = ParseAsWindowsAbsoluteFilePath (uriString);
1422 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1424 // Check scheme name characters as specified in RFC2396.
1425 // Note: different checks in 1.x and 2.0
1426 if (!CheckSchemeName (scheme))
1427 return Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
1429 // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1430 int startpos = pos + 1;
1431 int endpos = uriString.Length;
1434 pos = uriString.IndexOf ('#', startpos);
1435 if (!IsUnc && pos != -1) {
1437 fragment = uriString.Substring (pos);
1439 fragment = "#" + EscapeString (uriString.Substring (pos+1));
1445 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1447 query = uriString.Substring (pos, endpos-pos);
1450 query = EscapeString (query);
1454 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
1455 (endpos-startpos < 2) ||
1456 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))
1457 return "Invalid URI: The Authority/Host could not be parsed.";
1460 bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1461 bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1462 bool windowsFilePath = false;
1463 if (startsWithSlashSlash) {
1464 if (kind == UriKind.Relative)
1465 return "Absolute URI when we expected a relative one";
1467 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1470 if (scheme == UriSchemeFile) {
1471 int num_leading_slash = 2;
1472 for (int i = startpos; i < endpos; i++) {
1473 if (uriString [i] != '/')
1475 num_leading_slash++;
1477 if (num_leading_slash >= 4) {
1478 unixAbsPath = false;
1479 while (startpos < endpos && uriString[startpos] == '/') {
1482 } else if (num_leading_slash >= 3) {
1487 if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
1488 unixAbsPath = false;
1489 windowsFilePath = true;
1492 } else if (!IsPredefinedScheme (scheme)) {
1493 path = uriString.Substring(startpos, endpos-startpos);
1494 isOpaquePart = true;
1502 pos = uriString.IndexOf ('/', startpos, endpos-startpos);
1503 if (pos == -1 && windowsFilePath)
1504 pos = uriString.IndexOf ('\\', startpos, endpos-startpos);
1508 if ((scheme != Uri.UriSchemeMailto) &&
1510 (scheme != Uri.UriSchemeFile) &&
1512 (scheme != Uri.UriSchemeNews))
1515 path = uriString.Substring (pos, endpos-pos);
1523 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1525 userinfo = uriString.Substring (startpos, pos-startpos);
1534 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1535 if (pos != -1 && pos != endpos - 1) {
1536 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1537 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1539 if (!Int32.TryParse (portStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out port) ||
1540 port < 0 || port > UInt16.MaxValue)
1541 return "Invalid URI: Invalid port number";
1545 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1547 } catch (Exception) {
1548 return "Invalid URI: Invalid port number";
1553 port = GetDefaultPort (scheme);
1558 port = GetDefaultPort (scheme);
1563 uriString = uriString.Substring(startpos, endpos-startpos);
1567 path = Reduce ('/' + uriString);
1568 host = String.Empty;
1569 } else if (host.Length == 2 && host [1] == ':') {
1572 host = String.Empty;
1573 } else if (isUnixFilePath) {
1574 uriString = "//" + uriString;
1575 host = String.Empty;
1576 } else if (scheme == UriSchemeFile) {
1578 } else if (scheme == UriSchemeNews) {
1579 // no host for 'news', misinterpreted path
1580 if (host.Length > 0) {
1582 host = String.Empty;
1584 } else if (host.Length == 0 &&
1585 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
1586 scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1587 return "Invalid URI: The hostname could not be parsed";
1590 bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
1591 if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
1592 IPv6Address ipv6addr;
1594 if (IPv6Address.TryParse (host, out ipv6addr))
1595 host = "[" + ipv6addr.ToString (true) + "]";
1600 if (badhost && (Parser is DefaultUriParser || Parser == null))
1601 return Locale.GetText ("Invalid URI: The hostname could not be parsed. (" + host + ")");
1603 UriFormatException ex = null;
1605 Parser.InitializeAndValidate (this, out ex);
1610 return Locale.GetText ("Invalid URI: The hostname could not be parsed. (" + host + ")");
1613 if ((scheme != Uri.UriSchemeMailto) &&
1614 (scheme != Uri.UriSchemeNews) &&
1615 (scheme != Uri.UriSchemeFile)) {
1616 path = Reduce (path);
1622 private static string Reduce (string path)
1624 // quick out, allocation-free, for a common case
1628 // replace '\', %5C ('\') and %2f ('/') into '/'
1629 // other escaped values seems to survive this step
1630 StringBuilder res = new StringBuilder();
1631 for (int i=0; i < path.Length; i++) {
1638 if (i < path.Length - 2) {
1639 char c1 = path [i + 1];
1640 char c2 = Char.ToUpper (path [i + 2]);
1641 if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1656 path = res.ToString ();
1657 ArrayList result = new ArrayList ();
1659 for (int startpos = 0; startpos < path.Length; ) {
1660 int endpos = path.IndexOf('/', startpos);
1661 if (endpos == -1) endpos = path.Length;
1662 string current = path.Substring (startpos, endpos-startpos);
1663 startpos = endpos + 1;
1664 if (current.Length == 0 || current == "." )
1667 if (current == "..") {
1668 int resultCount = result.Count;
1670 // in 2.0 profile, skip leading ".." parts
1671 if (resultCount == 0) {
1675 result.RemoveAt (resultCount - 1);
1678 // in 1.x profile, retain leading ".." parts, and only reduce
1679 // URI is previous part is not ".."
1680 if (resultCount > 0) {
1681 if ((string) result[resultCount - 1] != "..") {
1682 result.RemoveAt (resultCount - 1);
1689 result.Add (current);
1692 if (result.Count == 0)
1696 if (path [0] == '/')
1700 foreach (string part in result) {
1709 if (path.EndsWith ("/"))
1712 return res.ToString();
1715 // A variant of HexUnescape() which can decode multi-byte escaped
1716 // sequences such as (e.g.) %E3%81%8B into a single character
1717 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1719 surrogate = char.MinValue;
1721 if (pattern == null)
1722 throw new ArgumentException ("pattern");
1724 if (index < 0 || index >= pattern.Length)
1725 throw new ArgumentOutOfRangeException ("index");
1727 if (!IsHexEncoding (pattern, index))
1728 return pattern [index++];
1730 int orig_index = index++;
1731 int msb = FromHex (pattern [index++]);
1732 int lsb = FromHex (pattern [index++]);
1734 // We might be dealing with a multi-byte character:
1735 // The number of ones at the top-end of the first byte will tell us
1736 // how many bytes will make up this character.
1739 while ((msb_copy & 0x8) == 0x8) {
1744 // We might be dealing with a single-byte character:
1745 // If there was only 0 or 1 leading ones then we're not dealing
1746 // with a multi-byte character.
1748 return (char) ((msb << 4) | lsb);
1750 // Now that we know how many bytes *should* follow, we'll check them
1751 // to ensure we are dealing with a valid multi-byte character.
1752 byte [] chars = new byte [num_bytes];
1753 bool all_invalid = false;
1754 chars[0] = (byte) ((msb << 4) | lsb);
1756 for (int i = 1; i < num_bytes; i++) {
1757 if (!IsHexEncoding (pattern, index++)) {
1762 // All following bytes must be in the form 10xxxxxx
1763 int cur_msb = FromHex (pattern [index++]);
1764 if ((cur_msb & 0xc) != 0x8) {
1769 int cur_lsb = FromHex (pattern [index++]);
1770 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1773 // If what looked like a multi-byte character is invalid, then we'll
1774 // just return the first byte as a single byte character.
1776 index = orig_index + 3;
1777 return (char) chars[0];
1780 // Otherwise, we're dealing with a valid multi-byte character.
1781 // We need to ignore the leading ones from the first byte:
1782 byte mask = (byte) 0xFF;
1783 mask >>= (num_bytes + 1);
1784 int result = chars[0] & mask;
1786 // The result will now be built up from the following bytes.
1787 for (int i = 1; i < num_bytes; i++) {
1788 // Ignore upper two bits
1790 result |= (chars[i] & 0x3F);
1793 if (result <= 0xFFFF) {
1794 return (char) result;
1796 // We need to handle this as a UTF16 surrogate (i.e. return
1799 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1800 return (char) ((result >> 10) | 0xD800);
1804 private struct UriScheme
1806 public string scheme;
1807 public string delimiter;
1808 public int defaultPort;
1810 public UriScheme (string s, string d, int p)
1818 static UriScheme [] schemes = new UriScheme [] {
1819 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1820 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1821 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1822 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1823 new UriScheme (UriSchemeMailto, ":", 25),
1824 new UriScheme (UriSchemeNews, ":", 119),
1825 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1826 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1829 internal static string GetSchemeDelimiter (string scheme)
1831 for (int i = 0; i < schemes.Length; i++)
1832 if (schemes [i].scheme == scheme)
1833 return schemes [i].delimiter;
1834 return Uri.SchemeDelimiter;
1837 internal static int GetDefaultPort (string scheme)
1840 UriParser parser = UriParser.GetParser (scheme);
1843 return parser.DefaultPort;
1845 for (int i = 0; i < schemes.Length; i++)
1846 if (schemes [i].scheme == scheme)
1847 return schemes [i].defaultPort;
1852 private string GetOpaqueWiseSchemeDelimiter ()
1857 return GetSchemeDelimiter (scheme);
1863 protected virtual bool IsBadFileSystemCharacter (char ch)
1865 // It does not always overlap with InvalidPathChars.
1866 int chInt = (int) ch;
1867 if (chInt < 32 || (chInt < 64 && chInt > 57))
1888 protected static bool IsExcludedCharacter (char ch)
1890 if (ch <= 32 || ch >= 127)
1893 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1894 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1895 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1901 internal static bool MaybeUri (string s)
1903 int p = s.IndexOf (':');
1910 return IsPredefinedScheme (s.Substring (0, p));
1913 private static bool IsPredefinedScheme (string scheme)
1937 protected virtual bool IsReservedCharacter (char ch)
1939 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1940 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1947 private UriParser parser;
1949 private UriParser Parser {
1951 if (parser == null) {
1952 parser = UriParser.GetParser (Scheme);
1953 // no specific parser ? then use a default one
1955 parser = new DefaultUriParser ("*");
1959 set { parser = value; }
1962 public string GetComponents (UriComponents components, UriFormat format)
1964 return Parser.GetComponents (this, components, format);
1967 public bool IsBaseOf (Uri uri)
1969 return Parser.IsBaseOf (this, uri);
1972 public bool IsWellFormedOriginalString ()
1974 // funny, but it does not use the Parser's IsWellFormedOriginalString().
1975 // Also, it seems we need to *not* escape hex.
1976 return EscapeString (OriginalString, false, false, true) == OriginalString;
1981 private const int MaxUriLength = 32766;
1983 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1985 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1986 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1987 throw new ArgumentException ("comparisonType", msg);
1990 if ((uri1 == null) && (uri2 == null))
1993 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1994 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1995 return String.Compare (s1, s2, comparisonType);
1999 // The rules for EscapeDataString
2001 static bool NeedToEscapeDataChar (char b)
2003 return !((b >= 'A' && b <= 'Z') ||
2004 (b >= 'a' && b <= 'z') ||
2005 (b >= '0' && b <= '9') ||
2006 b == '_' || b == '~' || b == '!' || b == '\'' ||
2007 b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
2010 public static string EscapeDataString (string stringToEscape)
2012 if (stringToEscape == null)
2013 throw new ArgumentNullException ("stringToEscape");
2015 if (stringToEscape.Length > MaxUriLength) {
2016 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2017 throw new UriFormatException (msg);
2019 bool escape = false;
2020 foreach (char c in stringToEscape){
2021 if (NeedToEscapeDataChar (c)){
2027 return stringToEscape;
2030 StringBuilder sb = new StringBuilder ();
2031 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2032 foreach (byte b in bytes){
2033 if (NeedToEscapeDataChar ((char) b))
2034 sb.Append (HexEscape ((char) b));
2036 sb.Append ((char) b);
2038 return sb.ToString ();
2042 // The rules for EscapeUriString
2044 static bool NeedToEscapeUriChar (char b)
2046 return !((b >= 'A' && b <= 'Z') ||
2047 (b >= 'a' && b <= 'z') ||
2048 (b >= '&' && b <= ';') ||
2049 b == '!' || b == '#' || b == '$' || b == '=' ||
2050 b == '?' || b == '@' || b == '_' || b == '~');
2053 public static string EscapeUriString (string stringToEscape)
2055 if (stringToEscape == null)
2056 throw new ArgumentNullException ("stringToEscape");
2058 if (stringToEscape.Length > MaxUriLength) {
2059 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2060 throw new UriFormatException (msg);
2063 bool escape = false;
2064 foreach (char c in stringToEscape){
2065 if (NeedToEscapeUriChar (c)){
2071 return stringToEscape;
2073 StringBuilder sb = new StringBuilder ();
2074 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2075 foreach (byte b in bytes){
2076 if (NeedToEscapeUriChar ((char) b))
2077 sb.Append (HexEscape ((char) b));
2079 sb.Append ((char) b);
2081 return sb.ToString ();
2084 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
2086 if (uriString == null)
2090 if (Uri.TryCreate (uriString, uriKind, out uri))
2091 return uri.IsWellFormedOriginalString ();
2095 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
2099 Uri r = new Uri (uriString, uriKind, out success);
2108 // [MonoTODO ("rework code to avoid exception catching")]
2109 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
2112 // FIXME: this should call UriParser.Resolve
2113 result = new Uri (baseUri, relativeUri);
2115 } catch (UriFormatException) {
2121 //[MonoTODO ("rework code to avoid exception catching")]
2122 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
2125 // FIXME: this should call UriParser.Resolve
2126 result = new Uri (baseUri, relativeUri.OriginalString);
2128 } catch (UriFormatException) {
2134 public static string UnescapeDataString (string stringToUnescape)
2136 if (stringToUnescape == null)
2137 throw new ArgumentNullException ("stringToUnescape");
2139 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
2140 return stringToUnescape;
2142 StringBuilder output = new StringBuilder ();
2143 long len = stringToUnescape.Length;
2144 MemoryStream bytes = new MemoryStream ();
2147 for (int i = 0; i < len; i++) {
2148 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
2149 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
2150 if (bytes.Length > 0) {
2151 output.Append (GetChars (bytes, Encoding.UTF8));
2152 bytes.SetLength (0);
2155 xchar = GetChar (stringToUnescape, i + 2, 4);
2157 output.Append ((char) xchar);
2161 output.Append ('%');
2164 else if ((xchar = GetChar (stringToUnescape, i + 1, 2)) != -1) {
2165 bytes.WriteByte ((byte) xchar);
2169 output.Append ('%');
2174 if (bytes.Length > 0) {
2175 output.Append (GetChars (bytes, Encoding.UTF8));
2176 bytes.SetLength (0);
2179 output.Append (stringToUnescape [i]);
2182 if (bytes.Length > 0) {
2183 output.Append (GetChars (bytes, Encoding.UTF8));
2187 return output.ToString ();
2190 private static int GetInt (byte b)
2193 if (c >= '0' && c <= '9')
2196 if (c >= 'a' && c <= 'f')
2197 return c - 'a' + 10;
2199 if (c >= 'A' && c <= 'F')
2200 return c - 'A' + 10;
2205 private static int GetChar (string str, int offset, int length)
2208 int end = length + offset;
2209 for (int i = offset; i < end; i++) {
2214 int current = GetInt ((byte) c);
2217 val = (val << 4) + current;
2223 private static char [] GetChars (MemoryStream b, Encoding e)
2225 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2229 private void EnsureAbsoluteUri ()
2232 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
2235 private void EnsureAbsoluteUri ()