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,2005 Novell, Inc (http://www.novell.com)
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 // See RFC 2396 for more info on URI's.
39 // TODO: optimize by parsing host string only once
41 using System.ComponentModel;
44 using System.Runtime.Serialization;
46 using System.Collections;
47 using System.Globalization;
50 // Disable warnings on Obsolete methods being used
52 #pragma warning disable 612
58 [TypeConverter (typeof (UriTypeConverter))]
59 public class Uri : ISerializable {
61 public class Uri : MarshalByRefObject, ISerializable {
64 // o scheme excludes the scheme delimiter
65 // o port is -1 to indicate no port is defined
66 // o path is empty or starts with / when scheme delimiter == "://"
67 // o query is empty or starts with ? char, escaped.
68 // o fragment is empty or starts with # char, unescaped.
69 // o all class variables are in escaped format when they are escapable,
70 // except cachedToString.
71 // o UNC is supported, as starts with "\\" for windows,
74 private bool isUnixFilePath;
75 private string source;
76 private string scheme = String.Empty;
77 private string host = String.Empty;
78 private int port = -1;
79 private string path = String.Empty;
80 private string query = String.Empty;
81 private string fragment = String.Empty;
82 private string userinfo = String.Empty;
84 private bool isOpaquePart;
85 private bool isAbsoluteUri = true;
87 private string [] segments;
89 private bool userEscaped;
90 private string cachedAbsoluteUri;
91 private string cachedToString;
92 private string cachedLocalPath;
93 private int cachedHashCode;
95 private static readonly string hexUpperChars = "0123456789ABCDEF";
99 public static readonly string SchemeDelimiter = "://";
100 public static readonly string UriSchemeFile = "file";
101 public static readonly string UriSchemeFtp = "ftp";
102 public static readonly string UriSchemeGopher = "gopher";
103 public static readonly string UriSchemeHttp = "http";
104 public static readonly string UriSchemeHttps = "https";
105 public static readonly string UriSchemeMailto = "mailto";
106 public static readonly string UriSchemeNews = "news";
107 public static readonly string UriSchemeNntp = "nntp";
109 public static readonly string UriSchemeNetPipe = "net.pipe";
110 public static readonly string UriSchemeNetTcp = "net.tcp";
116 public Uri (string uriString) : this (uriString, UriKind.Absolute)
120 public Uri (string uriString) : this (uriString, false)
124 protected Uri (SerializationInfo serializationInfo,
125 StreamingContext streamingContext) :
126 this (serializationInfo.GetString ("AbsoluteUri"), true)
131 public Uri (string uriString, UriKind uriKind)
137 case UriKind.Absolute:
139 throw new UriFormatException("Invalid URI: The format of the URI could not be "
142 case UriKind.Relative:
144 throw new UriFormatException("Invalid URI: The format of the URI could not be "
145 + "determined because the parameter 'uriString' represents an absolute URI.");
147 case UriKind.RelativeOrAbsolute:
150 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
151 throw new ArgumentException (msg);
156 // An exception-less constructor, returns success
157 // condition on the out parameter `success'.
159 Uri (string uriString, UriKind uriKind, out bool success)
161 if (uriString == null) {
166 if (uriKind != UriKind.RelativeOrAbsolute &&
167 uriKind != UriKind.Absolute &&
168 uriKind != UriKind.Relative) {
169 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
170 throw new ArgumentException (msg);
174 if (ParseNoExceptions (uriKind, uriString) != null)
180 case UriKind.Absolute:
184 case UriKind.Relative:
188 case UriKind.RelativeOrAbsolute:
197 public Uri (Uri baseUri, Uri relativeUri)
199 Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
200 // FIXME: this should call UriParser.Resolve
203 // note: doc says that dontEscape is always false but tests show otherwise
205 public Uri (string uriString, bool dontEscape)
207 userEscaped = dontEscape;
209 ParseUri (UriKind.Absolute);
211 throw new UriFormatException("Invalid URI: The format of the URI could not be "
212 + "determined: " + uriString);
215 public Uri (string uriString, bool dontEscape)
217 userEscaped = dontEscape;
221 throw new UriFormatException("Invalid URI: The format of the URI could not be "
226 public Uri (Uri baseUri, string relativeUri)
228 Merge (baseUri, relativeUri);
229 // FIXME: this should call UriParser.Resolve
233 [Obsolete ("dontEscape is always false")]
235 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
237 userEscaped = dontEscape;
238 Merge (baseUri, relativeUri);
241 private void Merge (Uri baseUri, string relativeUri)
245 throw new ArgumentNullException ("baseUri");
246 if (!baseUri.IsAbsoluteUri)
247 throw new ArgumentOutOfRangeException ("baseUri");
248 if (relativeUri == null)
249 relativeUri = String.Empty;
252 throw new NullReferenceException ("baseUri");
254 // See RFC 2396 Par 5.2 and Appendix C
256 // Check Windows UNC (for // it is scheme/host separator)
257 if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
258 source = relativeUri;
260 ParseUri (UriKind.Absolute);
267 int pos = relativeUri.IndexOf (':');
270 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
272 // pos2 < 0 ... e.g. mailto
273 // pos2 > pos ... to block ':' in query part
274 if (pos2 > pos || pos2 < 0) {
275 // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
276 // 1) when the URI scheme in the
277 // relative path is different from that
278 // of the baseUri, or
279 // 2) the URI scheme is non-standard
280 // ones (non-standard URIs are always
281 // treated as absolute here), or
282 // 3) the relative URI path is absolute.
283 if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
284 !IsPredefinedScheme (baseUri.Scheme) ||
285 relativeUri.Length > pos + 1 &&
286 relativeUri [pos + 1] == '/') {
287 source = relativeUri;
289 ParseUri (UriKind.Absolute);
296 relativeUri = relativeUri.Substring (pos + 1);
300 this.scheme = baseUri.scheme;
301 this.host = baseUri.host;
302 this.port = baseUri.port;
303 this.userinfo = baseUri.userinfo;
304 this.isUnc = baseUri.isUnc;
305 this.isUnixFilePath = baseUri.isUnixFilePath;
306 this.isOpaquePart = baseUri.isOpaquePart;
308 if (relativeUri == String.Empty) {
309 this.path = baseUri.path;
310 this.query = baseUri.query;
311 this.fragment = baseUri.fragment;
316 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
317 pos = relativeUri.IndexOf ('#');
320 fragment = relativeUri.Substring (pos);
322 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
323 relativeUri = relativeUri.Substring (0, pos);
327 pos = relativeUri.IndexOf ('?');
329 query = relativeUri.Substring (pos);
331 query = EscapeString (query);
332 relativeUri = relativeUri.Substring (0, pos);
335 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
336 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
337 source = scheme + ':' + relativeUri;
339 ParseUri (UriKind.Absolute);
347 path = EscapeString (path);
354 if (relativeUri.Length > 0 || query.Length > 0) {
355 pos = path.LastIndexOf ('/');
357 path = path.Substring (0, pos + 1);
360 if(relativeUri.Length == 0)
369 pos = path.IndexOf ("./", startIndex);
373 path = path.Remove (0, 2);
374 else if (path [pos - 1] != '.')
375 path = path.Remove (pos, 2);
377 startIndex = pos + 1;
381 if (path.Length > 1 &&
382 path [path.Length - 1] == '.' &&
383 path [path.Length - 2] == '/')
384 path = path.Remove (path.Length - 1, 1);
389 pos = path.IndexOf ("/../", startIndex);
396 int pos2 = path.LastIndexOf ('/', pos - 1);
398 startIndex = pos + 1;
400 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
401 path = path.Remove (pos2 + 1, pos - pos2 + 3);
403 startIndex = pos + 1;
408 if (path.Length > 3 && path.EndsWith ("/..")) {
409 pos = path.LastIndexOf ('/', path.Length - 4);
411 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
412 path = path.Remove (pos + 1, path.Length - pos - 1);
416 path = EscapeString (path);
421 public string AbsolutePath {
424 EnsureAbsoluteUri ();
428 // faster (mailto) and special (file) cases
431 if (path.Length == 0) {
432 string start = Scheme + SchemeDelimiter;
433 if (path.StartsWith (start))
446 public string AbsoluteUri {
448 EnsureAbsoluteUri ();
449 if (cachedAbsoluteUri == null) {
450 cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
451 if (query.Length > 0)
452 cachedAbsoluteUri += query;
453 if (fragment.Length > 0)
454 cachedAbsoluteUri += fragment;
456 return cachedAbsoluteUri;
460 public string Authority {
462 EnsureAbsoluteUri ();
463 return (GetDefaultPort (Scheme) == port)
464 ? host : host + ":" + port;
468 public string Fragment {
470 EnsureAbsoluteUri ();
477 EnsureAbsoluteUri ();
482 public UriHostNameType HostNameType {
484 EnsureAbsoluteUri ();
485 UriHostNameType ret = CheckHostName (Host);
486 if (ret != UriHostNameType.Unknown)
491 return UriHostNameType.Basic;
493 return (IsFile) ? UriHostNameType.Basic : ret;
496 // looks it always returns Basic...
497 return UriHostNameType.Basic; //.Unknown;
502 public bool IsDefaultPort {
504 EnsureAbsoluteUri ();
505 return GetDefaultPort (Scheme) == port;
511 EnsureAbsoluteUri ();
512 return (Scheme == UriSchemeFile);
516 public bool IsLoopback {
518 EnsureAbsoluteUri ();
520 if (Host.Length == 0) {
528 if (host == "loopback" || host == "localhost")
532 if (IPAddress.TryParse (host, out result))
533 if (IPAddress.Loopback.Equals (result))
537 if (IPv6Address.TryParse (host, out result6)){
538 if (IPv6Address.IsLoopback (result6))
547 // rule: This should be true only if
548 // - uri string starts from "\\", or
549 // - uri string starts from "//" (Samba way)
551 EnsureAbsoluteUri ();
556 public string LocalPath {
558 EnsureAbsoluteUri ();
559 if (cachedLocalPath != null)
560 return cachedLocalPath;
564 bool windows = (path.Length > 3 && path [1] == ':' &&
565 (path [2] == '\\' || path [2] == '/'));
568 string p = Unescape (path);
569 bool replace = windows;
571 replace |= (System.IO.Path.DirectorySeparatorChar == '\\');
574 cachedLocalPath = p.Replace ('/', '\\');
578 // support *nix and W32 styles
579 if (path.Length > 1 && path [1] == ':')
580 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
582 // LAMESPEC: ok, now we cannot determine
583 // if such URI like "file://foo/bar" is
584 // Windows UNC or unix file path, so
585 // they should be handled differently.
586 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
588 if (path.Length > 0) {
590 if ((path.Length > 1) || (path[0] != '/')) {
591 h += path.Replace ('/', '\\');
594 h += path.Replace ('/', '\\');
597 cachedLocalPath = "\\\\" + Unescape (h);
599 cachedLocalPath = Unescape (path);
601 if (cachedLocalPath.Length == 0)
602 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
603 return cachedLocalPath;
607 public string PathAndQuery {
609 EnsureAbsoluteUri ();
616 EnsureAbsoluteUri ();
621 public string Query {
623 EnsureAbsoluteUri ();
628 public string Scheme {
630 EnsureAbsoluteUri ();
635 public string [] Segments {
637 EnsureAbsoluteUri ();
638 if (segments != null)
641 if (path.Length == 0) {
642 segments = new string [0];
646 string [] parts = path.Split ('/');
648 bool endSlash = path.EndsWith ("/");
649 if (parts.Length > 0 && endSlash) {
650 string [] newParts = new string [parts.Length - 1];
651 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
656 if (IsFile && path.Length > 1 && path [1] == ':') {
657 string [] newParts = new string [parts.Length + 1];
658 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
660 parts [0] = path.Substring (0, 2);
661 parts [1] = String.Empty;
665 int end = parts.Length;
667 if (i != end - 1 || endSlash)
675 public bool UserEscaped {
676 get { return userEscaped; }
679 public string UserInfo {
681 EnsureAbsoluteUri ();
687 [MonoTODO ("add support for IPv6 address")]
688 public string DnsSafeHost {
690 EnsureAbsoluteUri ();
691 return Unescape (Host);
695 public bool IsAbsoluteUri {
696 get { return isAbsoluteUri; }
699 // LAMESPEC: source field is supplied in such case that this
700 // property makes sense. For such case that source field is
701 // not supplied (i.e. .ctor(Uri, string), this property
702 // makes no sense. To avoid silly regression it just returns
703 // ToString() value now. See bug #78374.
709 string OriginalString {
710 get { return source != null ? source : ToString (); }
715 public static UriHostNameType CheckHostName (string name)
717 if (name == null || name.Length == 0)
718 return UriHostNameType.Unknown;
720 if (IsIPv4Address (name))
721 return UriHostNameType.IPv4;
723 if (IsDomainAddress (name))
724 return UriHostNameType.Dns;
727 if (IPv6Address.TryParse (name, out addr))
728 return UriHostNameType.IPv6;
730 return UriHostNameType.Unknown;
733 internal static bool IsIPv4Address (string name)
735 string [] captures = name.Split (new char [] {'.'});
736 if (captures.Length != 4)
739 for (int i = 0; i < 4; i++) {
742 length = captures [i].Length;
747 if (!UInt32.TryParse (captures [i], out number))
751 number = UInt32.Parse (captures [i]);
752 } catch (Exception) {
762 internal static bool IsDomainAddress (string name)
764 int len = name.Length;
767 for (int i = 0; i < len; i++) {
770 if (!Char.IsLetterOrDigit (c))
772 } else if (c == '.') {
774 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
785 [Obsolete("This method does nothing, it has been obsoleted")]
787 protected virtual void Canonicalize ()
790 // This is flagged in the Microsoft documentation as used
791 // internally, no longer in use, and Obsolete.
795 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
796 public static bool CheckSchemeName (string schemeName)
798 if (schemeName == null || schemeName.Length == 0)
801 if (!IsAlpha (schemeName [0]))
804 int len = schemeName.Length;
805 for (int i = 1; i < len; i++) {
806 char c = schemeName [i];
807 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
814 private static bool IsAlpha (char c)
817 // as defined in rfc2234
818 // %x41-5A / %x61-7A (A-Z / a-z)
820 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
822 // Fx 1.x got this too large
823 return Char.IsLetter (c);
827 [MonoTODO ("Find out what this should do")]
831 protected virtual void CheckSecurity ()
835 public override bool Equals (object comparant)
837 if (comparant == null)
840 Uri uri = comparant as Uri;
841 if ((object) uri == null) {
842 string s = comparant as String;
848 return InternalEquals (uri);
851 // Assumes: uri != null
852 bool InternalEquals (Uri uri)
855 if (this.isAbsoluteUri != uri.isAbsoluteUri)
857 if (!this.isAbsoluteUri)
858 return this.source == uri.source;
861 CultureInfo inv = CultureInfo.InvariantCulture;
862 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
863 && this.host.ToLower (inv) == uri.host.ToLower (inv)
864 && this.port == uri.port
866 && this.query == uri.query
868 // Note: MS.NET 1.x has bug - ignores query check altogether
869 && this.query.ToLower (inv) == uri.query.ToLower (inv)
871 && this.path == uri.path;
875 public static bool operator == (Uri u1, Uri u2)
877 return object.Equals(u1, u2);
880 public static bool operator != (Uri u1, Uri u2)
886 public override int GetHashCode ()
888 if (cachedHashCode == 0) {
889 CultureInfo inv = CultureInfo.InvariantCulture;
891 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
892 ^ host.ToLower (inv).GetHashCode ()
895 ^ query.GetHashCode ()
897 ^ query.ToLower (inv).GetHashCode ()
899 ^ path.GetHashCode ();
902 cachedHashCode = source.GetHashCode ();
905 return cachedHashCode;
908 public string GetLeftPart (UriPartial part)
910 EnsureAbsoluteUri ();
913 case UriPartial.Scheme :
914 return scheme + GetOpaqueWiseSchemeDelimiter ();
915 case UriPartial.Authority :
916 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
919 StringBuilder s = new StringBuilder ();
921 s.Append (GetOpaqueWiseSchemeDelimiter ());
922 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
923 s.Append ('/'); // win32 file
924 if (userinfo.Length > 0)
925 s.Append (userinfo).Append ('@');
927 defaultPort = GetDefaultPort (scheme);
928 if ((port != -1) && (port != defaultPort))
929 s.Append (':').Append (port);
930 return s.ToString ();
931 case UriPartial.Path :
932 StringBuilder sb = new StringBuilder ();
934 sb.Append (GetOpaqueWiseSchemeDelimiter ());
935 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
936 sb.Append ('/'); // win32 file
937 if (userinfo.Length > 0)
938 sb.Append (userinfo).Append ('@');
940 defaultPort = GetDefaultPort (scheme);
941 if ((port != -1) && (port != defaultPort))
942 sb.Append (':').Append (port);
944 if (path.Length > 0) {
952 sb.Append (Reduce (path));
959 return sb.ToString ();
964 public static int FromHex (char digit)
966 if ('0' <= digit && digit <= '9') {
967 return (int) (digit - '0');
970 if ('a' <= digit && digit <= 'f')
971 return (int) (digit - 'a' + 10);
973 if ('A' <= digit && digit <= 'F')
974 return (int) (digit - 'A' + 10);
976 throw new ArgumentException ("digit");
979 public static string HexEscape (char character)
981 if (character > 255) {
982 throw new ArgumentOutOfRangeException ("character");
985 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
986 + hexUpperChars [((character & 0x0f))];
989 public static char HexUnescape (string pattern, ref int index)
992 throw new ArgumentException ("pattern");
994 if (index < 0 || index >= pattern.Length)
995 throw new ArgumentOutOfRangeException ("index");
997 if (!IsHexEncoding (pattern, index))
998 return pattern [index++];
1001 int msb = FromHex (pattern [index++]);
1002 int lsb = FromHex (pattern [index++]);
1003 return (char) ((msb << 4) | lsb);
1006 public static bool IsHexDigit (char digit)
1008 return (('0' <= digit && digit <= '9') ||
1009 ('a' <= digit && digit <= 'f') ||
1010 ('A' <= digit && digit <= 'F'));
1013 public static bool IsHexEncoding (string pattern, int index)
1015 if ((index + 3) > pattern.Length)
1018 return ((pattern [index++] == '%') &&
1019 IsHexDigit (pattern [index++]) &&
1020 IsHexDigit (pattern [index]));
1025 // Implemented by copying most of the MakeRelative code
1027 public Uri MakeRelativeUri (Uri uri)
1030 throw new ArgumentNullException ("uri");
1032 if (Host != uri.Host || Scheme != uri.Scheme)
1035 string result = String.Empty;
1036 if (this.path != uri.path){
1037 string [] segments = this.Segments;
1038 string [] segments2 = uri.Segments;
1041 int max = Math.Min (segments.Length, segments2.Length);
1042 for (; k < max; k++)
1043 if (segments [k] != segments2 [k])
1046 for (int i = k + 1; i < segments.Length; i++)
1048 for (int i = k; i < segments2.Length; i++)
1049 result += segments2 [i];
1052 uri.AppendQueryAndFragment (ref result);
1054 return new Uri (result, UriKind.Relative);
1057 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1059 public string MakeRelative (Uri toUri)
1061 if ((this.Scheme != toUri.Scheme) ||
1062 (this.Authority != toUri.Authority))
1063 return toUri.ToString ();
1065 string result = String.Empty;
1066 if (this.path != toUri.path){
1067 string [] segments = this.Segments;
1068 string [] segments2 = toUri.Segments;
1070 int max = Math.Min (segments.Length, segments2.Length);
1071 for (; k < max; k++)
1072 if (segments [k] != segments2 [k])
1075 for (int i = k + 1; i < segments.Length; i++)
1077 for (int i = k; i < segments2.Length; i++)
1078 result += segments2 [i];
1081 // Important: MakeRelative does not append fragment or query.
1086 void AppendQueryAndFragment (ref string result)
1088 if (query.Length > 0) {
1089 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), false) : Unescape (query, false);
1092 if (fragment.Length > 0)
1096 public override string ToString ()
1098 if (cachedToString != null)
1099 return cachedToString;
1102 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1104 // Everything is contained in path in this case.
1105 cachedToString = Unescape (path);
1108 AppendQueryAndFragment (ref cachedToString);
1109 return cachedToString;
1113 protected void GetObjectData (SerializationInfo info, StreamingContext context)
1115 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1119 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1121 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
1130 protected virtual void Escape ()
1132 path = EscapeString (path);
1138 protected static string EscapeString (string str)
1140 return EscapeString (str, false, true, true);
1143 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
1146 return String.Empty;
1148 StringBuilder s = new StringBuilder ();
1149 int len = str.Length;
1150 for (int i = 0; i < len; i++) {
1151 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1152 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1153 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1154 // space = <US-ASCII coded character 20 hexadecimal>
1155 // delims = "<" | ">" | "#" | "%" | <">
1156 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1158 // check for escape code already placed in str,
1159 // i.e. for encoding that follows the pattern
1160 // "%hexhex" in a string, where "hex" is a digit from 0-9
1161 // or a letter from A-F (case-insensitive).
1162 if (IsHexEncoding (str,i)) {
1163 // if ,yes , copy it as is
1164 s.Append(str.Substring (i, 3));
1169 byte [] data = Encoding.UTF8.GetBytes (new char[] {str[i]});
1170 int length = data.Length;
1171 for (int j = 0; j < length; j++) {
1172 char c = (char) data [j];
1173 if ((c <= 0x20) || (c >= 0x7f) ||
1174 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
1175 (escapeHex && (c == '#')) ||
1176 (escapeBrackets && (c == '[' || c == ']')) ||
1177 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
1178 s.Append (HexEscape (c));
1185 return s.ToString ();
1188 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1189 // can avoid the "absolute uri" constraints of the .ctor() by
1190 // overriding with custom code.
1192 [Obsolete("The method has been deprecated. It is not used by the system.")]
1194 protected virtual void Parse ()
1197 ParseUri (UriKind.Absolute);
1201 private void ParseUri (UriKind kind)
1203 Parse (kind, source);
1208 host = EscapeString (host, false, true, false);
1209 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1210 // host name present (but not an IPv6 address)
1211 host = host.ToLower (CultureInfo.InvariantCulture);
1214 if (path.Length > 0) {
1215 path = EscapeString (path);
1222 protected virtual string Unescape (string str)
1224 return Unescape (str, false);
1227 internal static string Unescape (string str, bool excludeSpecial)
1230 return String.Empty;
1231 StringBuilder s = new StringBuilder ();
1232 int len = str.Length;
1233 for (int i = 0; i < len; i++) {
1237 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1238 if (excludeSpecial && x == '#')
1240 else if (excludeSpecial && x == '%')
1242 else if (excludeSpecial && x == '?')
1246 if (surrogate != char.MinValue)
1247 s.Append (surrogate);
1253 return s.ToString ();
1259 private void ParseAsWindowsUNC (string uriString)
1261 scheme = UriSchemeFile;
1263 fragment = String.Empty;
1264 query = String.Empty;
1267 uriString = uriString.TrimStart (new char [] {'\\'});
1268 int pos = uriString.IndexOf ('\\');
1270 path = uriString.Substring (pos);
1271 host = uriString.Substring (0, pos);
1272 } else { // "\\\\server"
1274 path = String.Empty;
1276 path = path.Replace ("\\", "/");
1280 // Returns null on success, string with error on failure
1282 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1284 if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1285 return "Relative file path is not allowed.";
1286 scheme = UriSchemeFile;
1287 host = String.Empty;
1289 path = uriString.Replace ("\\", "/");
1290 fragment = String.Empty;
1291 query = String.Empty;
1296 private void ParseAsUnixAbsoluteFilePath (string uriString)
1298 isUnixFilePath = true;
1299 scheme = UriSchemeFile;
1301 fragment = String.Empty;
1302 query = String.Empty;
1303 host = String.Empty;
1306 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1307 uriString = uriString.TrimStart (new char [] {'/'});
1308 // Now we don't regard //foo/bar as "foo" host.
1310 int pos = uriString.IndexOf ('/');
1312 path = '/' + uriString.Substring (pos + 1);
1313 host = uriString.Substring (0, pos);
1314 } else { // "///server"
1316 path = String.Empty;
1319 path = '/' + uriString;
1326 // This parse method will throw exceptions on failure
1328 private void Parse (UriKind kind, string uriString)
1330 if (uriString == null)
1331 throw new ArgumentNullException ("uriString");
1333 string s = ParseNoExceptions (kind, uriString);
1335 throw new UriFormatException (s);
1339 // This parse method will not throw exceptions on failure
1341 // Returns null on success, or a description of the error in the parsing
1343 private string ParseNoExceptions (UriKind kind, string uriString)
1348 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1352 uriString = uriString.Trim();
1353 int len = uriString.Length;
1356 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1357 isAbsoluteUri = false;
1362 if (len <= 1 && (kind != UriKind.Relative))
1363 return "Absolute URI is too short";
1368 // Identify Windows path, unix path, or standard URI.
1369 pos = uriString.IndexOf (':');
1371 return "Invalid URI: The format of the URI could not be determined.";
1372 } else if (pos < 0) {
1373 // It must be Unix file path or Windows UNC
1374 if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1375 ParseAsUnixAbsoluteFilePath (uriString);
1377 isAbsoluteUri = false;
1379 if (kind == UriKind.Relative)
1380 isAbsoluteUri = false;
1383 } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\')
1384 ParseAsWindowsUNC (uriString);
1387 isAbsoluteUri = false;
1391 } else if (pos == 1) {
1392 if (!IsAlpha (uriString [0]))
1393 return "URI scheme must start with a letter.";
1394 // This means 'a:' == windows full path.
1395 string msg = ParseAsWindowsAbsoluteFilePath (uriString);
1402 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1404 // Check scheme name characters as specified in RFC2396.
1405 // Note: different checks in 1.x and 2.0
1406 if (!CheckSchemeName (scheme))
1407 return Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
1409 // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1410 int startpos = pos + 1;
1411 int endpos = uriString.Length;
1414 pos = uriString.IndexOf ('#', startpos);
1415 if (!IsUnc && pos != -1) {
1417 fragment = uriString.Substring (pos);
1419 fragment = "#" + EscapeString (uriString.Substring (pos+1));
1425 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1427 query = uriString.Substring (pos, endpos-pos);
1430 query = EscapeString (query);
1434 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
1435 (endpos-startpos < 2) ||
1436 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))
1437 return "Invalid URI: The Authority/Host could not be parsed.";
1440 bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1441 bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1442 bool windowsFilePath = false;
1443 if (startsWithSlashSlash) {
1444 if (kind == UriKind.Relative)
1445 return "Absolute URI when we expected a relative one";
1447 if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
1450 if (scheme == UriSchemeFile) {
1451 int num_leading_slash = 2;
1452 for (int i = startpos; i < endpos; i++) {
1453 if (uriString [i] != '/')
1455 num_leading_slash++;
1457 if (num_leading_slash >= 4) {
1458 unixAbsPath = false;
1459 while (startpos < endpos && uriString[startpos] == '/') {
1462 } else if (num_leading_slash >= 3) {
1467 if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
1468 unixAbsPath = false;
1469 windowsFilePath = true;
1472 } else if (!IsPredefinedScheme (scheme)) {
1473 path = uriString.Substring(startpos, endpos-startpos);
1474 isOpaquePart = true;
1482 pos = uriString.IndexOf ('/', startpos, endpos-startpos);
1483 if (pos == -1 && windowsFilePath)
1484 pos = uriString.IndexOf ('\\', startpos, endpos-startpos);
1488 if ((scheme != Uri.UriSchemeMailto) &&
1490 (scheme != Uri.UriSchemeFile) &&
1492 (scheme != Uri.UriSchemeNews))
1495 path = uriString.Substring (pos, endpos-pos);
1500 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1502 userinfo = uriString.Substring (startpos, pos-startpos);
1511 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1512 if (pos != -1 && pos != endpos - 1) {
1513 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1514 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1516 if (!Int32.TryParse (portStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out port) ||
1517 port < 0 || port > UInt16.MaxValue)
1518 return "Invalid URI: Invalid port number";
1522 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1524 } catch (Exception) {
1525 return "Invalid URI: Invalid port number";
1530 port = GetDefaultPort (scheme);
1535 port = GetDefaultPort (scheme);
1540 uriString = uriString.Substring(startpos, endpos-startpos);
1544 path = Reduce ('/' + uriString);
1545 host = String.Empty;
1546 } else if (host.Length == 2 && host [1] == ':') {
1549 host = String.Empty;
1550 } else if (isUnixFilePath) {
1551 uriString = "//" + uriString;
1552 host = String.Empty;
1553 } else if (scheme == UriSchemeFile) {
1555 } else if (scheme == UriSchemeNews) {
1556 // no host for 'news', misinterpreted path
1557 if (host.Length > 0) {
1559 host = String.Empty;
1561 } else if (host.Length == 0 &&
1562 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
1563 scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1564 return "Invalid URI: The hostname could not be parsed";
1567 bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
1568 if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
1569 IPv6Address ipv6addr;
1571 if (IPv6Address.TryParse (host, out ipv6addr))
1572 host = "[" + ipv6addr.ToString (true) + "]";
1577 if (badhost && (Parser is DefaultUriParser || Parser == null))
1578 return Locale.GetText ("Invalid URI: The hostname could not be parsed. (" + host + ")");
1580 UriFormatException ex = null;
1582 Parser.InitializeAndValidate (this, out ex);
1587 return Locale.GetText ("Invalid URI: The hostname could not be parsed. (" + host + ")");
1590 if ((scheme != Uri.UriSchemeMailto) &&
1591 (scheme != Uri.UriSchemeNews) &&
1592 (scheme != Uri.UriSchemeFile)) {
1593 path = Reduce (path);
1599 private static string Reduce (string path)
1601 // quick out, allocation-free, for a common case
1605 // replace '\', %5C ('\') and %2f ('/') into '/'
1606 // other escaped values seems to survive this step
1607 StringBuilder res = new StringBuilder();
1608 for (int i=0; i < path.Length; i++) {
1615 if (i < path.Length - 2) {
1616 char c1 = path [i + 1];
1617 char c2 = Char.ToUpper (path [i + 2]);
1618 if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1633 path = res.ToString ();
1634 ArrayList result = new ArrayList ();
1636 for (int startpos = 0; startpos < path.Length; ) {
1637 int endpos = path.IndexOf('/', startpos);
1638 if (endpos == -1) endpos = path.Length;
1639 string current = path.Substring (startpos, endpos-startpos);
1640 startpos = endpos + 1;
1641 if (current.Length == 0 || current == "." )
1644 if (current == "..") {
1645 int resultCount = result.Count;
1647 // in 2.0 profile, skip leading ".." parts
1648 if (resultCount == 0) {
1652 result.RemoveAt (resultCount - 1);
1655 // in 1.x profile, retain leading ".." parts, and only reduce
1656 // URI is previous part is not ".."
1657 if (resultCount > 0) {
1658 if ((string) result[resultCount - 1] != "..") {
1659 result.RemoveAt (resultCount - 1);
1666 result.Add (current);
1669 if (result.Count == 0)
1673 if (path [0] == '/')
1677 foreach (string part in result) {
1686 if (path.EndsWith ("/"))
1689 return res.ToString();
1692 // A variant of HexUnescape() which can decode multi-byte escaped
1693 // sequences such as (e.g.) %E3%81%8B into a single character
1694 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1696 surrogate = char.MinValue;
1698 if (pattern == null)
1699 throw new ArgumentException ("pattern");
1701 if (index < 0 || index >= pattern.Length)
1702 throw new ArgumentOutOfRangeException ("index");
1704 if (!IsHexEncoding (pattern, index))
1705 return pattern [index++];
1707 int orig_index = index++;
1708 int msb = FromHex (pattern [index++]);
1709 int lsb = FromHex (pattern [index++]);
1711 // We might be dealing with a multi-byte character:
1712 // The number of ones at the top-end of the first byte will tell us
1713 // how many bytes will make up this character.
1716 while ((msb_copy & 0x8) == 0x8) {
1721 // We might be dealing with a single-byte character:
1722 // If there was only 0 or 1 leading ones then we're not dealing
1723 // with a multi-byte character.
1725 return (char) ((msb << 4) | lsb);
1727 // Now that we know how many bytes *should* follow, we'll check them
1728 // to ensure we are dealing with a valid multi-byte character.
1729 byte [] chars = new byte [num_bytes];
1730 bool all_invalid = false;
1731 chars[0] = (byte) ((msb << 4) | lsb);
1733 for (int i = 1; i < num_bytes; i++) {
1734 if (!IsHexEncoding (pattern, index++)) {
1739 // All following bytes must be in the form 10xxxxxx
1740 int cur_msb = FromHex (pattern [index++]);
1741 if ((cur_msb & 0xc) != 0x8) {
1746 int cur_lsb = FromHex (pattern [index++]);
1747 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1750 // If what looked like a multi-byte character is invalid, then we'll
1751 // just return the first byte as a single byte character.
1753 index = orig_index + 3;
1754 return (char) chars[0];
1757 // Otherwise, we're dealing with a valid multi-byte character.
1758 // We need to ignore the leading ones from the first byte:
1759 byte mask = (byte) 0xFF;
1760 mask >>= (num_bytes + 1);
1761 int result = chars[0] & mask;
1763 // The result will now be built up from the following bytes.
1764 for (int i = 1; i < num_bytes; i++) {
1765 // Ignore upper two bits
1767 result |= (chars[i] & 0x3F);
1770 if (result <= 0xFFFF) {
1771 return (char) result;
1773 // We need to handle this as a UTF16 surrogate (i.e. return
1776 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1777 return (char) ((result >> 10) | 0xD800);
1781 private struct UriScheme
1783 public string scheme;
1784 public string delimiter;
1785 public int defaultPort;
1787 public UriScheme (string s, string d, int p)
1795 static UriScheme [] schemes = new UriScheme [] {
1796 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1797 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1798 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1799 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1800 new UriScheme (UriSchemeMailto, ":", 25),
1801 new UriScheme (UriSchemeNews, ":", 119),
1802 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1803 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1806 internal static string GetSchemeDelimiter (string scheme)
1808 for (int i = 0; i < schemes.Length; i++)
1809 if (schemes [i].scheme == scheme)
1810 return schemes [i].delimiter;
1811 return Uri.SchemeDelimiter;
1814 internal static int GetDefaultPort (string scheme)
1817 UriParser parser = UriParser.GetParser (scheme);
1820 return parser.DefaultPort;
1822 for (int i = 0; i < schemes.Length; i++)
1823 if (schemes [i].scheme == scheme)
1824 return schemes [i].defaultPort;
1829 private string GetOpaqueWiseSchemeDelimiter ()
1834 return GetSchemeDelimiter (scheme);
1840 protected virtual bool IsBadFileSystemCharacter (char ch)
1842 // It does not always overlap with InvalidPathChars.
1843 int chInt = (int) ch;
1844 if (chInt < 32 || (chInt < 64 && chInt > 57))
1865 protected static bool IsExcludedCharacter (char ch)
1867 if (ch <= 32 || ch >= 127)
1870 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1871 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1872 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1878 internal static bool MaybeUri (string s)
1880 int p = s.IndexOf (':');
1887 return IsPredefinedScheme (s.Substring (0, p));
1890 private static bool IsPredefinedScheme (string scheme)
1914 protected virtual bool IsReservedCharacter (char ch)
1916 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1917 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1924 private UriParser parser;
1926 private UriParser Parser {
1929 parser = UriParser.GetParser (Scheme);
1932 set { parser = value; }
1935 public string GetComponents (UriComponents components, UriFormat format)
1937 return Parser.GetComponents (this, components, format);
1940 public bool IsBaseOf (Uri uri)
1942 return Parser.IsBaseOf (this, uri);
1945 public bool IsWellFormedOriginalString ()
1947 // funny, but it does not use the Parser's IsWellFormedOriginalString().
1948 return EscapeString (OriginalString) == OriginalString;
1953 private const int MaxUriLength = 32766;
1955 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
1957 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
1958 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
1959 throw new ArgumentException ("comparisonType", msg);
1962 if ((uri1 == null) && (uri2 == null))
1965 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
1966 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
1967 return String.Compare (s1, s2, comparisonType);
1971 // The rules for EscapeDataString
1973 static bool NeedToEscapeDataChar (char b)
1975 return !((b >= 'A' && b <= 'Z') ||
1976 (b >= 'a' && b <= 'z') ||
1977 (b >= '0' && b <= '9') ||
1978 b == '_' || b == '~' || b == '!' || b == '\'' ||
1979 b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
1982 public static string EscapeDataString (string stringToEscape)
1984 if (stringToEscape == null)
1985 throw new ArgumentNullException ("stringToEscape");
1987 if (stringToEscape.Length > MaxUriLength) {
1988 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
1989 throw new UriFormatException (msg);
1991 bool escape = false;
1992 foreach (char c in stringToEscape){
1993 if (NeedToEscapeDataChar (c)){
1999 return stringToEscape;
2002 StringBuilder sb = new StringBuilder ();
2003 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2004 foreach (byte b in bytes){
2005 if (NeedToEscapeDataChar ((char) b))
2006 sb.Append (HexEscape ((char) b));
2008 sb.Append ((char) b);
2010 return sb.ToString ();
2014 // The rules for EscapeUriString
2016 static bool NeedToEscapeUriChar (char b)
2018 return !((b >= 'A' && b <= 'Z') ||
2019 (b >= 'a' && b <= 'z') ||
2020 (b >= '&' && b <= ';') ||
2021 b == '!' || b == '#' || b == '$' || b == '=' ||
2022 b == '?' || b == '@' || b == '_' || b == '~');
2025 public static string EscapeUriString (string stringToEscape)
2027 if (stringToEscape == null)
2028 throw new ArgumentNullException ("stringToEscape");
2030 if (stringToEscape.Length > MaxUriLength) {
2031 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2032 throw new UriFormatException (msg);
2035 bool escape = false;
2036 foreach (char c in stringToEscape){
2037 if (NeedToEscapeUriChar (c)){
2043 return stringToEscape;
2045 StringBuilder sb = new StringBuilder ();
2046 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2047 foreach (byte b in bytes){
2048 if (NeedToEscapeUriChar ((char) b))
2049 sb.Append (HexEscape ((char) b));
2051 sb.Append ((char) b);
2053 return sb.ToString ();
2056 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
2058 if (uriString == null)
2062 if (Uri.TryCreate (uriString, uriKind, out uri))
2063 return uri.IsWellFormedOriginalString ();
2067 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
2071 Uri r = new Uri (uriString, uriKind, out success);
2080 // [MonoTODO ("rework code to avoid exception catching")]
2081 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
2084 // FIXME: this should call UriParser.Resolve
2085 result = new Uri (baseUri, relativeUri);
2087 } catch (UriFormatException) {
2093 //[MonoTODO ("rework code to avoid exception catching")]
2094 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
2097 // FIXME: this should call UriParser.Resolve
2098 result = new Uri (baseUri, relativeUri);
2100 } catch (UriFormatException) {
2106 public static string UnescapeDataString (string stringToUnescape)
2108 if (stringToUnescape == null)
2109 throw new ArgumentNullException ("stringToUnescape");
2111 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
2112 return stringToUnescape;
2114 StringBuilder output = new StringBuilder ();
2115 long len = stringToUnescape.Length;
2116 MemoryStream bytes = new MemoryStream ();
2119 for (int i = 0; i < len; i++) {
2120 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
2121 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
2122 if (bytes.Length > 0) {
2123 output.Append (GetChars (bytes, Encoding.UTF8));
2124 bytes.SetLength (0);
2127 xchar = GetChar (stringToUnescape, i + 2, 4);
2129 output.Append ((char) xchar);
2133 output.Append ('%');
2136 else if ((xchar = GetChar (stringToUnescape, i + 1, 2)) != -1) {
2137 bytes.WriteByte ((byte) xchar);
2141 output.Append ('%');
2146 if (bytes.Length > 0) {
2147 output.Append (GetChars (bytes, Encoding.UTF8));
2148 bytes.SetLength (0);
2151 output.Append (stringToUnescape [i]);
2154 if (bytes.Length > 0) {
2155 output.Append (GetChars (bytes, Encoding.UTF8));
2159 return output.ToString ();
2162 private static int GetInt (byte b)
2165 if (c >= '0' && c <= '9')
2168 if (c >= 'a' && c <= 'f')
2169 return c - 'a' + 10;
2171 if (c >= 'A' && c <= 'F')
2172 return c - 'A' + 10;
2177 private static int GetChar (string str, int offset, int length)
2180 int end = length + offset;
2181 for (int i = offset; i < end; i++) {
2186 int current = GetInt ((byte) c);
2189 val = (val << 4) + current;
2195 private static char [] GetChars (MemoryStream b, Encoding e)
2197 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2201 private void EnsureAbsoluteUri ()
2204 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
2207 private void EnsureAbsoluteUri ()