5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Garrett Rooney (rooneg@electricjellyfish.net)
7 // Ian MacLean (ianm@activestate.com)
8 // Ben Maurer (bmaurer@users.sourceforge.net)
9 // Atsushi Enomoto (atsushi@ximian.com)
10 // Sebastien Pouliot <sebastien@ximian.com>
11 // Stephane Delcroix <stephane@delcroix.org>
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com)
17 // Copyright (c) 2009 Stephane Delcroix
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 // See RFC 2396 for more info on URI's.
40 // TODO: optimize by parsing host string only once
42 using System.ComponentModel;
45 using System.Runtime.Serialization;
47 using System.Collections;
48 using System.Collections.Generic;
49 using System.Globalization;
52 // Disable warnings on Obsolete methods being used
54 #pragma warning disable 612
59 [TypeConverter (typeof (UriTypeConverter))]
60 public class Uri : ISerializable {
62 // o scheme excludes the scheme delimiter
63 // o port is -1 to indicate no port is defined
64 // o path is empty or starts with / when scheme delimiter == "://"
65 // o query is empty or starts with ? char, escaped.
66 // o fragment is empty or starts with # char, unescaped.
67 // o all class variables are in escaped format when they are escapable,
68 // except cachedToString.
69 // o UNC is supported, as starts with "\\" for windows,
72 private bool isUnixFilePath;
73 private string source;
74 private string scheme = String.Empty;
75 private string host = String.Empty;
76 private int port = -1;
77 private string path = String.Empty;
78 private string query = String.Empty;
79 private string fragment = String.Empty;
80 private string userinfo;
82 private bool isOpaquePart;
83 private bool isAbsoluteUri = true;
84 private long scope_id;
86 private List<string> segments;
88 private bool userEscaped;
89 private string cachedAbsoluteUri;
90 private string cachedToString;
91 private string cachedLocalPath;
92 private int cachedHashCode;
94 private static readonly string hexUpperChars = "0123456789ABCDEF";
95 private static readonly string [] Empty = new string [0];
96 private static bool isWin32 = (Path.DirectorySeparatorChar == '\\');
101 public static readonly string SchemeDelimiter = "://";
102 public static readonly string UriSchemeFile = "file";
103 public static readonly string UriSchemeFtp = "ftp";
104 public static readonly string UriSchemeGopher = "gopher";
105 public static readonly string UriSchemeHttp = "http";
106 public static readonly string UriSchemeHttps = "https";
107 public static readonly string UriSchemeMailto = "mailto";
108 public static readonly string UriSchemeNews = "news";
109 public static readonly string UriSchemeNntp = "nntp";
110 public static readonly string UriSchemeNetPipe = "net.pipe";
111 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, StreamingContext streamingContext)
126 string uri = serializationInfo.GetString ("AbsoluteUri");
127 if (uri.Length > 0) {
129 ParseUri(UriKind.Absolute);
131 uri = serializationInfo.GetString ("RelativeUri");
132 if (uri.Length > 0) {
134 ParseUri(UriKind.Relative);
136 throw new ArgumentException("Uri string was null or empty.");
141 public Uri (string uriString, UriKind uriKind)
147 case UriKind.Absolute:
149 throw new UriFormatException("Invalid URI: The format of the URI could not be "
152 case UriKind.Relative:
154 throw new UriFormatException("Invalid URI: The format of the URI could not be "
155 + "determined because the parameter 'uriString' represents an absolute URI.");
157 case UriKind.RelativeOrAbsolute:
160 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
161 throw new ArgumentException (msg);
166 // An exception-less constructor, returns success
167 // condition on the out parameter `success'.
169 Uri (string uriString, UriKind uriKind, out bool success)
171 if (uriString == null) {
176 if (uriKind != UriKind.RelativeOrAbsolute &&
177 uriKind != UriKind.Absolute &&
178 uriKind != UriKind.Relative) {
179 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
180 throw new ArgumentException (msg);
184 if (ParseNoExceptions (uriKind, uriString) != null)
190 case UriKind.Absolute:
194 case UriKind.Relative:
198 case UriKind.RelativeOrAbsolute:
207 public Uri (Uri baseUri, Uri relativeUri)
209 Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
210 // FIXME: this should call UriParser.Resolve
213 // note: doc says that dontEscape is always false but tests show otherwise
215 public Uri (string uriString, bool dontEscape)
217 userEscaped = dontEscape;
219 ParseUri (UriKind.Absolute);
221 throw new UriFormatException("Invalid URI: The format of the URI could not be "
222 + "determined: " + uriString);
225 public Uri (Uri baseUri, string relativeUri)
227 Merge (baseUri, relativeUri);
228 // FIXME: this should call UriParser.Resolve
231 [Obsolete ("dontEscape is always false")]
232 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
234 userEscaped = dontEscape;
235 Merge (baseUri, relativeUri);
238 private void Merge (Uri baseUri, string relativeUri)
241 throw new ArgumentNullException ("baseUri");
242 if (!baseUri.IsAbsoluteUri)
243 throw new ArgumentOutOfRangeException ("baseUri");
244 if (relativeUri == null)
245 relativeUri = String.Empty;
247 // See RFC 2396 Par 5.2 and Appendix C
249 // Check Windows UNC (for // it is scheme/host separator)
250 if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
251 source = relativeUri;
252 ParseUri (UriKind.Absolute);
256 int pos = relativeUri.IndexOf (':');
259 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
261 // pos2 < 0 ... e.g. mailto
262 // pos2 > pos ... to block ':' in query part
263 if (pos2 > pos || pos2 < 0) {
264 // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
265 // 1) when the URI scheme in the
266 // relative path is different from that
267 // of the baseUri, or
268 // 2) the URI scheme is non-standard
269 // ones (non-standard URIs are always
270 // treated as absolute here), or
271 // 3) the relative URI path is absolute.
272 if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
273 !IsPredefinedScheme (baseUri.Scheme) ||
274 (relativeUri.Length > pos + 1 && relativeUri [pos + 1] == '/')) {
276 if (Uri.TryCreate (relativeUri, UriKind.Absolute, out tmp)) {
277 source = relativeUri;
278 ParseUri (UriKind.Absolute);
280 } else if (pos == 1) {
281 // special case as this looks like a windows path
282 string msg = ParseAsWindowsAbsoluteFilePath (relativeUri);
284 throw new UriFormatException (msg);
286 // otherwise continue with 'full' relativeUri
289 relativeUri = relativeUri.Substring (pos + 1);
293 this.scheme = baseUri.scheme;
294 this.host = baseUri.host;
295 this.port = baseUri.port;
296 this.userinfo = baseUri.userinfo;
297 this.isUnc = baseUri.isUnc;
298 this.isUnixFilePath = baseUri.isUnixFilePath;
299 this.isOpaquePart = baseUri.isOpaquePart;
301 if (relativeUri.Length == 0) {
302 this.path = baseUri.path;
303 this.query = baseUri.query;
304 this.fragment = baseUri.fragment;
309 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
310 string original_fragment = String.Empty;
311 pos = relativeUri.IndexOf ('#');
313 original_fragment = relativeUri.Substring (pos);
315 fragment = original_fragment;
317 fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
318 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
321 bool consider_query = false;
324 pos = relativeUri.IndexOf ('?');
326 query = relativeUri.Substring (pos);
328 query = EscapeString (query);
329 #if !NET_4_0 && !MOONLIGHT && !MOBILE
330 consider_query = query.Length > 0;
332 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
333 } else if (relativeUri.Length == 0) {
334 // if there is no relative path then we keep the Query and Fragment from the absolute
335 query = baseUri.query;
338 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
339 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
340 source = scheme + ':' + relativeUri;
341 ParseUri (UriKind.Absolute);
346 path = EscapeString (path);
353 if ((relativeUri.Length > 0) || consider_query) {
354 pos = path.LastIndexOf ('/');
356 path = path.Substring (0, pos + 1);
359 if (relativeUri.Length == 0) {
360 // when merging URI the OriginalString is not quite original
361 source = GetLeftPart (UriPartial.Authority) + query + original_fragment;
371 pos = path.IndexOf ("./", startIndex);
375 path = path.Remove (0, 2);
376 else if (path [pos - 1] != '.')
377 path = path.Remove (pos, 2);
379 startIndex = pos + 1;
383 if (path.Length > 1 &&
384 path [path.Length - 1] == '.' &&
385 path [path.Length - 2] == '/')
386 path = path.Remove (path.Length - 1, 1);
391 pos = path.IndexOf ("/../", startIndex);
398 int pos2 = path.LastIndexOf ('/', pos - 1);
400 startIndex = pos + 1;
402 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
403 path = path.Remove (pos2 + 1, pos - pos2 + 3);
405 startIndex = pos + 1;
410 if (path.Length > 3 && path.EndsWith ("/..")) {
411 pos = path.LastIndexOf ('/', path.Length - 4);
413 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
414 path = path.Remove (pos + 1, path.Length - pos - 1);
418 while (path.StartsWith ("/../", StringComparison.Ordinal))
419 path = path.Substring (3);
422 path = EscapeString (path);
424 // when merging URI the OriginalString is not quite original
425 source = GetLeftPart (UriPartial.Authority) + path + query + original_fragment;
430 public string AbsolutePath {
432 EnsureAbsoluteUri ();
433 if (scheme == "mailto" || scheme == "file")
434 // faster (mailto) and special (file) cases
437 if (path.Length == 0) {
438 string start = scheme + SchemeDelimiter;
439 if (path.StartsWith (start, StringComparison.Ordinal))
448 public string AbsoluteUri {
450 EnsureAbsoluteUri ();
451 if (cachedAbsoluteUri == null) {
452 cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
453 if (query.Length > 0)
454 cachedAbsoluteUri += query;
455 if (fragment.Length > 0)
456 cachedAbsoluteUri += fragment;
458 return cachedAbsoluteUri;
462 public string Authority {
464 EnsureAbsoluteUri ();
465 return (GetDefaultPort (Scheme) == port)
466 ? host : host + ":" + port;
470 public string Fragment {
472 EnsureAbsoluteUri ();
479 EnsureAbsoluteUri ();
484 public UriHostNameType HostNameType {
486 EnsureAbsoluteUri ();
487 UriHostNameType ret = CheckHostName (Host);
488 if (ret != UriHostNameType.Unknown)
491 if (scheme == "mailto")
492 return UriHostNameType.Basic;
493 return (IsFile) ? UriHostNameType.Basic : ret;
497 public bool IsDefaultPort {
499 EnsureAbsoluteUri ();
500 return GetDefaultPort (Scheme) == port;
506 EnsureAbsoluteUri ();
507 return (Scheme == UriSchemeFile);
511 public bool IsLoopback {
513 EnsureAbsoluteUri ();
515 if (Host.Length == 0) {
519 if (host == "loopback" || host == "localhost")
523 if (IPAddress.TryParse (host, out result))
524 if (IPAddress.Loopback.Equals (result))
528 if (IPv6Address.TryParse (host, out result6)){
529 if (IPv6Address.IsLoopback (result6))
538 // rule: This should be true only if
539 // - uri string starts from "\\", or
540 // - uri string starts from "//" (Samba way)
542 EnsureAbsoluteUri ();
547 private bool IsLocalIdenticalToAbsolutePath ()
552 if ((scheme == Uri.UriSchemeNews) || (scheme == Uri.UriSchemeNntp) || (scheme == Uri.UriSchemeFtp))
555 return IsWellFormedOriginalString ();
558 public string LocalPath {
560 EnsureAbsoluteUri ();
561 if (cachedLocalPath != null)
562 return cachedLocalPath;
564 if (IsLocalIdenticalToAbsolutePath ()) {
565 cachedLocalPath = Unescape (AbsolutePath);
566 return cachedLocalPath;
570 string p = Unescape (path);
571 bool windows = (path.Length > 3 && path [1] == ':' &&
572 (path [2] == '\\' || path [2] == '/'));
575 cachedLocalPath = p.Replace ('/', '\\');
579 // support *nix and W32 styles
580 if (path.Length > 1 && path [1] == ':')
581 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
583 // LAMESPEC: ok, now we cannot determine
584 // if such URI like "file://foo/bar" is
585 // Windows UNC or unix file path, so
586 // they should be handled differently.
587 else if (System.IO.Path.DirectorySeparatorChar == '\\') {
589 if (path.Length > 0) {
590 if ((path.Length > 1) || (path[0] != '/')) {
591 h += path.Replace ('/', '\\');
594 cachedLocalPath = "\\\\" + Unescape (h);
596 cachedLocalPath = Unescape (path);
598 if (cachedLocalPath.Length == 0)
599 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
600 return cachedLocalPath;
604 public string PathAndQuery {
606 EnsureAbsoluteUri ();
613 EnsureAbsoluteUri ();
618 public string Query {
620 EnsureAbsoluteUri ();
625 public string Scheme {
627 EnsureAbsoluteUri ();
632 public string [] Segments {
634 EnsureAbsoluteUri ();
636 // return a (pre-allocated) empty array
637 if (path.Length == 0)
639 // do not return the original array (since items can be changed)
640 if (segments != null)
641 return segments.ToArray ();
643 List<string> list = new List<string> ();
644 StringBuilder current = new StringBuilder ();
645 for (int i = 0; i < path.Length; i++) {
649 current.Append (path [i]);
650 list.Add (current.ToString ());
654 if ((i < path.Length - 2) && (path [i + 1] == '5' && path [i + 2] == 'C')) {
655 current.Append ("%5C");
656 list.Add (current.ToString ());
660 current.Append ('%');
664 current.Append (path [i]);
669 if (current.Length > 0)
670 list.Add (current.ToString ());
672 if (IsFile && (list.Count > 0)) {
673 string first = list [0];
674 if ((first.Length > 1) && (first [1] == ':')) {
675 list.Insert (0, "/");
679 return segments.ToArray ();
683 public bool UserEscaped {
684 get { return userEscaped; }
687 public string UserInfo {
689 EnsureAbsoluteUri ();
690 return userinfo == null ? String.Empty : userinfo;
694 public string DnsSafeHost {
696 EnsureAbsoluteUri ();
698 if (HostNameType == UriHostNameType.IPv6) {
699 host = Host.Substring (1, Host.Length - 2);
701 host += "%" + scope_id.ToString ();
703 return Unescape (host);
713 get { return isAbsoluteUri; }
716 // LAMESPEC: source field is supplied in such case that this
717 // property makes sense. For such case that source field is
718 // not supplied (i.e. .ctor(Uri, string), this property
719 // makes no sense. To avoid silly regression it just returns
720 // ToString() value now. See bug #78374.
721 public string OriginalString {
722 get { return source != null ? source : ToString (); }
727 public static UriHostNameType CheckHostName (string name)
729 if (name == null || name.Length == 0)
730 return UriHostNameType.Unknown;
732 if (IsIPv4Address (name))
733 return UriHostNameType.IPv4;
735 if (IsDomainAddress (name))
736 return UriHostNameType.Dns;
739 if (IPv6Address.TryParse (name, out addr))
740 return UriHostNameType.IPv6;
742 return UriHostNameType.Unknown;
745 internal static bool IsIPv4Address (string name)
747 string [] captures = name.Split (new char [] {'.'});
748 if (captures.Length != 4)
751 for (int i = 0; i < 4; i++) {
754 length = captures [i].Length;
758 if (!UInt32.TryParse (captures [i], out number))
766 internal static bool IsDomainAddress (string name)
768 int len = name.Length;
771 for (int i = 0; i < len; i++) {
774 if (!Char.IsLetterOrDigit (c))
776 } else if (c == '.') {
777 // www..host.com is bad
778 if (i + 1 < len && name [i + 1] == '.')
781 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
792 [Obsolete("This method does nothing, it has been obsoleted")]
793 protected virtual void Canonicalize ()
796 // This is flagged in the Microsoft documentation as used
797 // internally, no longer in use, and Obsolete.
801 [MonoTODO ("Find out what this should do")]
803 protected virtual void CheckSecurity ()
809 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
810 public static bool CheckSchemeName (string schemeName)
812 if (schemeName == null || schemeName.Length == 0)
815 if (!IsAlpha (schemeName [0]))
818 int len = schemeName.Length;
819 for (int i = 1; i < len; i++) {
820 char c = schemeName [i];
821 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
828 private static bool IsAlpha (char c)
830 // as defined in rfc2234
831 // %x41-5A / %x61-7A (A-Z / a-z)
833 return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
836 public override bool Equals (object comparand)
838 if (comparand == null)
841 Uri uri = comparand as Uri;
842 if ((object) uri == null) {
843 string s = comparand as String;
847 if (!TryCreate (s, UriKind.RelativeOrAbsolute, out uri))
851 return InternalEquals (uri);
854 // Assumes: uri != null
855 bool InternalEquals (Uri uri)
857 if (this.isAbsoluteUri != uri.isAbsoluteUri)
859 if (!this.isAbsoluteUri)
860 return this.source == uri.source;
862 CultureInfo inv = CultureInfo.InvariantCulture;
863 return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
864 && this.host.ToLower (inv) == uri.host.ToLower (inv)
865 && this.port == uri.port
866 && this.query == uri.query
867 && this.path == uri.path;
870 public static bool operator == (Uri uri1, Uri uri2)
872 return object.Equals (uri1, uri2);
875 public static bool operator != (Uri uri1, Uri uri2)
877 return !(uri1 == uri2);
880 public override int GetHashCode ()
882 if (cachedHashCode == 0) {
883 CultureInfo inv = CultureInfo.InvariantCulture;
885 cachedHashCode = scheme.ToLower (inv).GetHashCode ()
886 ^ host.ToLower (inv).GetHashCode ()
888 ^ query.GetHashCode ()
889 ^ path.GetHashCode ();
892 cachedHashCode = source.GetHashCode ();
895 return cachedHashCode;
898 public string GetLeftPart (UriPartial part)
900 EnsureAbsoluteUri ();
903 case UriPartial.Scheme :
904 return scheme + GetOpaqueWiseSchemeDelimiter ();
905 case UriPartial.Authority :
906 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
909 StringBuilder s = new StringBuilder ();
911 s.Append (GetOpaqueWiseSchemeDelimiter ());
912 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
913 s.Append ('/'); // win32 file
914 if (userinfo != null)
915 s.Append (userinfo).Append ('@');
917 defaultPort = GetDefaultPort (scheme);
918 if ((port != -1) && (port != defaultPort))
919 s.Append (':').Append (port);
920 return s.ToString ();
921 case UriPartial.Path :
922 StringBuilder sb = new StringBuilder ();
924 sb.Append (GetOpaqueWiseSchemeDelimiter ());
925 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
926 sb.Append ('/'); // win32 file
927 if (userinfo != null)
928 sb.Append (userinfo).Append ('@');
930 defaultPort = GetDefaultPort (scheme);
931 if ((port != -1) && (port != defaultPort))
932 sb.Append (':').Append (port);
934 if (path.Length > 0) {
935 if (scheme == "mailto" || scheme == "news")
938 sb.Append (Reduce (path, CompactEscaped (scheme)));
940 return sb.ToString ();
945 public static int FromHex (char digit)
947 if ('0' <= digit && digit <= '9') {
948 return (int) (digit - '0');
951 if ('a' <= digit && digit <= 'f')
952 return (int) (digit - 'a' + 10);
954 if ('A' <= digit && digit <= 'F')
955 return (int) (digit - 'A' + 10);
957 throw new ArgumentException ("digit");
960 public static string HexEscape (char character)
962 if (character > 255) {
963 throw new ArgumentOutOfRangeException ("character");
966 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
967 + hexUpperChars [((character & 0x0f))];
970 public static char HexUnescape (string pattern, ref int index)
973 throw new ArgumentException ("pattern");
975 if (index < 0 || index >= pattern.Length)
976 throw new ArgumentOutOfRangeException ("index");
978 if (!IsHexEncoding (pattern, index))
979 return pattern [index++];
982 int msb = FromHex (pattern [index++]);
983 int lsb = FromHex (pattern [index++]);
984 return (char) ((msb << 4) | lsb);
987 public static bool IsHexDigit (char character)
989 return (('0' <= character && character <= '9') ||
990 ('a' <= character && character <= 'f') ||
991 ('A' <= character && character <= 'F'));
994 public static bool IsHexEncoding (string pattern, int index)
996 if ((index + 3) > pattern.Length)
999 return ((pattern [index++] == '%') &&
1000 IsHexDigit (pattern [index++]) &&
1001 IsHexDigit (pattern [index]));
1005 // Implemented by copying most of the MakeRelative code
1007 public Uri MakeRelativeUri (Uri uri)
1009 #if NET_4_0 || MOONLIGHT || MOBILE
1011 throw new ArgumentNullException ("uri");
1013 if (Host != uri.Host || Scheme != uri.Scheme)
1016 string result = String.Empty;
1017 if (this.path != uri.path){
1018 string [] segments = this.Segments;
1019 string [] segments2 = uri.Segments;
1022 int max = Math.Min (segments.Length, segments2.Length);
1023 for (; k < max; k++)
1024 if (segments [k] != segments2 [k])
1027 for (int i = k + 1; i < segments.Length; i++)
1029 for (int i = k; i < segments2.Length; i++)
1030 result += segments2 [i];
1033 uri.AppendQueryAndFragment (ref result);
1035 return new Uri (result, UriKind.Relative);
1038 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1039 public string MakeRelative (Uri toUri)
1041 if ((this.Scheme != toUri.Scheme) ||
1042 (this.Authority != toUri.Authority))
1043 return toUri.ToString ();
1045 string result = String.Empty;
1046 if (this.path != toUri.path){
1047 string [] segments = this.Segments;
1048 string [] segments2 = toUri.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 // Important: MakeRelative does not append fragment or query.
1066 void AppendQueryAndFragment (ref string result)
1068 if (query.Length > 0) {
1069 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true, false) : Unescape (query, false);
1072 if (fragment.Length > 0)
1073 result += Unescape (fragment, true, false);
1076 public override string ToString ()
1078 if (cachedToString != null)
1079 return cachedToString;
1081 if (isAbsoluteUri) {
1082 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1083 AppendQueryAndFragment (ref cachedToString);
1085 // Everything is contained in path in this case.
1086 cachedToString = path;
1089 return cachedToString;
1092 protected void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
1094 if (this.isAbsoluteUri) {
1095 serializationInfo.AddValue ("AbsoluteUri", this.AbsoluteUri);
1097 serializationInfo.AddValue("AbsoluteUri", String.Empty);
1098 serializationInfo.AddValue("RelativeUri", this.OriginalString);
1102 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1104 GetObjectData (info, context);
1111 protected virtual void Escape ()
1113 path = EscapeString (path);
1117 static string EscapeString (string str)
1120 protected static string EscapeString (string str)
1123 return EscapeString (str, Uri.EscapeCommonHexBrackets);
1126 private const string EscapeCommon = "<>%\"{}|\\^`";
1127 private const string EscapeReserved = ";/?:@&=+$,";
1128 private const string EscapeFragment = "#";
1129 private const string EscapeBrackets = "[]";
1131 private const string EscapeNews = EscapeCommon + EscapeBrackets + "?";
1132 private const string EscapeCommonHex = EscapeCommon + EscapeFragment;
1133 private const string EscapeCommonBrackets = EscapeCommon + EscapeBrackets;
1134 internal const string EscapeCommonHexBrackets = EscapeCommon + EscapeFragment + EscapeBrackets;
1135 internal const string EscapeCommonHexBracketsQuery = EscapeCommonHexBrackets + "?";
1137 internal static string EscapeString (string str, string escape)
1139 return EscapeString (str, escape, true);
1142 internal static string EscapeString (string str, string escape, bool nonAsciiEscape)
1144 if (String.IsNullOrEmpty (str))
1145 return String.Empty;
1147 StringBuilder s = new StringBuilder ();
1148 int len = str.Length;
1149 for (int i = 0; i < len; i++) {
1150 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1151 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1152 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1153 // space = <US-ASCII coded character 20 hexadecimal>
1154 // delims = "<" | ">" | "#" | "%" | <">
1155 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1157 // check for escape code already placed in str,
1158 // i.e. for encoding that follows the pattern
1159 // "%hexhex" in a string, where "hex" is a digit from 0-9
1160 // or a letter from A-F (case-insensitive).
1161 if (IsHexEncoding (str,i)) {
1162 // if ,yes , copy it as is
1163 s.Append(str.Substring (i, 3));
1169 bool outside_limited_ascii = ((c <= 0x20) || (c >= 0x7f));
1170 bool needs_escape = (escape.IndexOf (c) != -1);
1171 if (nonAsciiEscape && outside_limited_ascii) {
1172 byte [] data = Encoding.UTF8.GetBytes (new char [] { c });
1173 int length = data.Length;
1174 for (int j = 0; j < length; j++) {
1175 c = (char) data [j];
1176 if (needs_escape || nonAsciiEscape)
1177 s.Append (HexEscape (c));
1181 } else if (needs_escape) {
1182 s.Append (HexEscape (c));
1188 return s.ToString ();
1191 // On .NET 1.x, this method is called from .ctor(). When overriden, we
1192 // can avoid the "absolute uri" constraints of the .ctor() by
1193 // overriding with custom code.
1194 [Obsolete("The method has been deprecated. It is not used by the system.")]
1195 protected virtual void Parse ()
1199 private void ParseUri (UriKind kind)
1201 Parse (kind, source);
1206 // non-ascii characters are not escaped for the host name
1207 host = EscapeString (host, EscapeCommonHex, false);
1208 if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1209 // host name present (but not an IPv6 address)
1210 host = host.ToLower (CultureInfo.InvariantCulture);
1213 if (isAbsoluteUri && (path.Length > 0))
1214 path = EscapeString (path);
1218 string Unescape (string path)
1221 protected virtual string Unescape (string path)
1224 return Unescape (path, false, false);
1227 internal static string Unescape (string str, bool excludeSpecial)
1229 return Unescape (str, excludeSpecial, excludeSpecial);
1232 internal static string Unescape (string str, bool excludeSpecial, bool excludeBackslash)
1234 if (String.IsNullOrEmpty (str))
1235 return String.Empty;
1237 StringBuilder s = new StringBuilder ();
1238 int len = str.Length;
1239 for (int i = 0; i < len; i++) {
1243 char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1244 if (excludeSpecial && x == '#')
1246 else if (excludeSpecial && x == '%')
1248 else if (excludeSpecial && x == '?')
1250 else if (excludeBackslash && x == '\\')
1254 if (surrogate != char.MinValue)
1255 s.Append (surrogate);
1261 return s.ToString ();
1267 private void ParseAsWindowsUNC (string uriString)
1269 scheme = UriSchemeFile;
1271 fragment = String.Empty;
1272 query = String.Empty;
1275 uriString = uriString.TrimStart (new char [] {'\\'});
1276 int pos = uriString.IndexOf ('\\');
1278 path = uriString.Substring (pos);
1279 host = uriString.Substring (0, pos);
1280 } else { // "\\\\server"
1282 path = String.Empty;
1284 path = path.Replace ("\\", "/");
1288 // Returns null on success, string with error on failure
1290 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1292 if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1293 return "Relative file path is not allowed.";
1294 scheme = UriSchemeFile;
1295 host = String.Empty;
1297 path = uriString.Replace ("\\", "/");
1298 fragment = String.Empty;
1299 query = String.Empty;
1304 private void ParseAsUnixAbsoluteFilePath (string uriString)
1306 isUnixFilePath = true;
1307 scheme = UriSchemeFile;
1309 fragment = String.Empty;
1310 query = String.Empty;
1311 host = String.Empty;
1314 if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1315 uriString = uriString.TrimStart (new char [] {'/'});
1316 // Now we don't regard //foo/bar as "foo" host.
1318 int pos = uriString.IndexOf ('/');
1320 path = '/' + uriString.Substring (pos + 1);
1321 host = uriString.Substring (0, pos);
1322 } else { // "///server"
1324 path = String.Empty;
1327 path = '/' + uriString;
1334 // This parse method will throw exceptions on failure
1336 private void Parse (UriKind kind, string uriString)
1338 if (uriString == null)
1339 throw new ArgumentNullException ("uriString");
1341 string s = ParseNoExceptions (kind, uriString);
1343 throw new UriFormatException (s);
1346 private bool SupportsQuery ()
1348 return ((scheme != Uri.UriSchemeNntp) && (scheme != Uri.UriSchemeFtp) && (scheme != Uri.UriSchemeFile));
1352 // This parse method will not throw exceptions on failure
1354 // Returns null on success, or a description of the error in the parsing
1356 private string ParseNoExceptions (UriKind kind, string uriString)
1361 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1365 uriString = uriString.Trim();
1366 int len = uriString.Length;
1369 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1370 isAbsoluteUri = false;
1375 if (len <= 1 && (kind == UriKind.Absolute))
1376 return "Absolute URI is too short";
1381 // Identify Windows path, unix path, or standard URI.
1382 if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1384 ParseAsUnixAbsoluteFilePath (uriString);
1386 isAbsoluteUri = false;
1388 if (kind == UriKind.Relative)
1389 isAbsoluteUri = false;
1392 } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\') {
1394 ParseAsWindowsUNC (uriString);
1399 pos = uriString.IndexOf (':');
1401 if (kind == UriKind.Absolute)
1402 return "Invalid URI: The format of the URI could not be determined.";
1403 isAbsoluteUri = false;
1406 } else if (pos < 0) {
1408 isAbsoluteUri = false;
1411 } else if (pos == 1) {
1412 if (!IsAlpha (uriString [0])) {
1413 if (kind == UriKind.Absolute)
1414 return "Invalid URI: The URI scheme is not valid.";
1415 isAbsoluteUri = false;
1419 // This means 'a:' == windows full path.
1420 string msg = ParseAsWindowsAbsoluteFilePath (uriString);
1427 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1429 // Check scheme name characters as specified in RFC2396.
1430 // Note: different checks in 1.x and 2.0
1431 if (!CheckSchemeName (scheme)) {
1432 if (kind == UriKind.Absolute)
1433 return "Invalid URI: The URI scheme is not valid.";
1434 isAbsoluteUri = false;
1439 // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1440 int startpos = pos + 1;
1441 int endpos = uriString.Length;
1444 pos = uriString.IndexOf ('#', startpos);
1445 if (!IsUnc && pos != -1) {
1447 fragment = uriString.Substring (pos);
1449 fragment = "#" + EscapeString (uriString.Substring (pos+1));
1454 // special case: there is no query part for 'news'
1455 if (scheme == Uri.UriSchemeNews) {
1456 pos = scheme.Length + 1;
1457 path = EscapeString (uriString.Substring (pos, endpos - pos), EscapeNews);
1461 // special case: there is no query part for 'nntp', 'file' and 'ftp' but there is an host, port, user...
1462 if (SupportsQuery ()) {
1464 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1466 query = uriString.Substring (pos, endpos-pos);
1469 query = EscapeString (query);
1474 if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && (
1475 (endpos-startpos < 2) ||
1476 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))
1477 return "Invalid URI: The Authority/Host could not be parsed.";
1480 bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1481 bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1482 bool windowsFilePath = false;
1483 if (startsWithSlashSlash) {
1484 if (kind == UriKind.Relative)
1485 return "Absolute URI when we expected a relative one";
1487 if (scheme != UriSchemeMailto)
1490 if (scheme == UriSchemeFile) {
1491 int num_leading_slash = 2;
1492 for (int i = startpos; i < endpos; i++) {
1493 if (uriString [i] != '/')
1495 num_leading_slash++;
1497 if (num_leading_slash >= 4) {
1498 unixAbsPath = false;
1499 while (startpos < endpos && uriString[startpos] == '/') {
1502 } else if (num_leading_slash >= 3) {
1507 if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
1508 unixAbsPath = false;
1509 windowsFilePath = true;
1512 } else if (!IsPredefinedScheme (scheme)) {
1513 path = uriString.Substring(startpos, endpos-startpos);
1514 isOpaquePart = true;
1522 pos = uriString.IndexOf ('/', startpos, endpos - startpos);
1523 if (pos == -1 && windowsFilePath)
1524 pos = uriString.IndexOf ('\\', startpos, endpos - startpos);
1527 path = uriString.Substring (pos, endpos - pos);
1528 if (!SupportsQuery ()) {
1529 if (scheme != Uri.UriSchemeNntp)
1530 path = path.Replace ('\\', '/');
1531 path = EscapeString (path, EscapeNews);
1535 if (scheme != Uri.UriSchemeMailto)
1543 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1545 // supplying username / password on a file URI is not supported
1546 if (scheme == UriSchemeFile)
1547 return "Invalid host";
1548 userinfo = uriString.Substring (startpos, pos-startpos);
1553 bool valid_port = true;
1558 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1559 if (pos != -1 && pos != endpos - 1) {
1560 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1561 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1562 if (!Int32.TryParse (portStr, NumberStyles.None, CultureInfo.InvariantCulture, out port) ||
1563 port < 0 || port > UInt16.MaxValue)
1564 valid_port = false; // delay reporting
1569 port = GetDefaultPort (scheme);
1572 } else if (!IsFile) {
1573 // if no port is specified by a colon ':' is present then we must ignore it
1574 // since it would be part of the host name and, as such, would be invalid
1575 if (pos == endpos - 1)
1579 port = GetDefaultPort (scheme);
1583 uriString = uriString.Substring(startpos, endpos-startpos);
1587 path = Reduce ('/' + uriString, true);
1588 host = String.Empty;
1589 } else if (host.Length == 2 && host [1] == ':') {
1590 if (scheme != UriSchemeFile) {
1591 host = host [0].ToString ();
1595 host = String.Empty;
1597 } else if (isUnixFilePath) {
1598 uriString = "//" + uriString;
1599 host = String.Empty;
1600 } else if (scheme == UriSchemeFile) {
1601 // under Windows all file:// URI are considered UNC, which is not the case other MacOS (e.g. Silverlight)
1603 } else if (host.Length == 0 &&
1604 (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
1605 scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1606 return "Invalid URI: The Authority/Host could not be parsed.";
1609 if (host.Length > 0) {
1610 switch (CheckHostName (host)) {
1611 case UriHostNameType.Unknown:
1612 if ((host [0] == ':') || (host [0] == '@'))
1613 return "Invalid URI: The hostname could not be parsed.";
1614 if (host.IndexOf (':') != -1)
1615 return "Invalid URI: Invalid port specified.";
1616 if (Parser is DefaultUriParser || Parser == null)
1617 return "Invalid URI: The hostname could not be parsed.";
1619 case UriHostNameType.IPv6:
1620 IPv6Address ipv6addr;
1621 if (IPv6Address.TryParse (host, out ipv6addr)) {
1622 host = "[" + ipv6addr.ToString (true) + "]";
1623 scope_id = ipv6addr.ScopeId;
1628 // delayed reporting (to throw the expected exception in the right order)
1630 return "Invalid URI: Invalid port number";
1632 UriFormatException ex = null;
1634 Parser.InitializeAndValidate (this, out ex);
1638 if ((scheme != Uri.UriSchemeMailto) && (scheme != Uri.UriSchemeFile)) {
1639 path = Reduce (path, CompactEscaped (scheme));
1645 private static bool CompactEscaped (string scheme)
1647 if (scheme == null || scheme.Length < 4)
1650 char first = scheme [0];
1652 return scheme == "http" || scheme == "https";
1653 } else if (first == 'f' && scheme == "file"){
1655 } else if (first == 'n')
1656 return scheme == "net.pipe" || scheme == "net.tcp";
1661 // replace '\', %5C ('\') and %2f ('/') into '/'
1662 // replace %2e ('.') into '.'
1663 private static string NormalizePath (string path)
1665 StringBuilder res = new StringBuilder ();
1666 for (int i = 0; i < path.Length; i++) {
1673 if (i < path.Length - 2) {
1674 char c1 = path [i + 1];
1675 char c2 = Char.ToUpper (path [i + 2]);
1676 if ((c1 == '2') && (c2 == 'E')) {
1679 } else if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1688 return res.ToString ();
1691 // This is called "compacting" in the MSDN documentation
1692 private static string Reduce (string path, bool compact_escaped)
1694 // quick out, allocation-free, for a common case
1698 if (compact_escaped && (path.IndexOf ('%') != -1)) {
1699 // replace '\', %2f, %5c with '/' and replace %2e with '.'
1700 // other escaped values seems to survive this step
1701 path = NormalizePath (path);
1703 // (always) replace '\' with '/'
1704 path = path.Replace ('\\', '/');
1707 List<string> result = new List<string> ();
1710 for (int startpos = 0; startpos < path.Length; ) {
1711 int endpos = path.IndexOf ('/', startpos);
1713 endpos = path.Length;
1714 string current = path.Substring (startpos, endpos-startpos);
1715 startpos = endpos + 1;
1716 if ((begin && current.Length == 0) || current == "." ) {
1722 if (current == "..") {
1723 int resultCount = result.Count;
1724 // in 2.0 profile, skip leading ".." parts
1725 if (resultCount == 0) {
1729 result.RemoveAt (resultCount - 1);
1733 result.Add (current);
1736 if (result.Count == 0)
1739 StringBuilder res = new StringBuilder ();
1741 if (path [0] == '/')
1745 foreach (string part in result) {
1754 if (path [path.Length - 1] == '/')
1757 return res.ToString();
1760 // A variant of HexUnescape() which can decode multi-byte escaped
1761 // sequences such as (e.g.) %E3%81%8B into a single character
1762 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate)
1764 surrogate = char.MinValue;
1766 if (pattern == null)
1767 throw new ArgumentException ("pattern");
1769 if (index < 0 || index >= pattern.Length)
1770 throw new ArgumentOutOfRangeException ("index");
1772 if (!IsHexEncoding (pattern, index))
1773 return pattern [index++];
1775 int orig_index = index++;
1776 int msb = FromHex (pattern [index++]);
1777 int lsb = FromHex (pattern [index++]);
1779 // We might be dealing with a multi-byte character:
1780 // The number of ones at the top-end of the first byte will tell us
1781 // how many bytes will make up this character.
1784 while ((msb_copy & 0x8) == 0x8) {
1789 // We might be dealing with a single-byte character:
1790 // If there was only 0 or 1 leading ones then we're not dealing
1791 // with a multi-byte character.
1793 return (char) ((msb << 4) | lsb);
1795 // Now that we know how many bytes *should* follow, we'll check them
1796 // to ensure we are dealing with a valid multi-byte character.
1797 byte [] chars = new byte [num_bytes];
1798 bool all_invalid = false;
1799 chars[0] = (byte) ((msb << 4) | lsb);
1801 for (int i = 1; i < num_bytes; i++) {
1802 if (!IsHexEncoding (pattern, index++)) {
1807 // All following bytes must be in the form 10xxxxxx
1808 int cur_msb = FromHex (pattern [index++]);
1809 if ((cur_msb & 0xc) != 0x8) {
1814 int cur_lsb = FromHex (pattern [index++]);
1815 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1818 // If what looked like a multi-byte character is invalid, then we'll
1819 // just return the first byte as a single byte character.
1821 index = orig_index + 3;
1822 return (char) chars[0];
1825 // Otherwise, we're dealing with a valid multi-byte character.
1826 // We need to ignore the leading ones from the first byte:
1827 byte mask = (byte) 0xFF;
1828 mask >>= (num_bytes + 1);
1829 int result = chars[0] & mask;
1831 // The result will now be built up from the following bytes.
1832 for (int i = 1; i < num_bytes; i++) {
1833 // Ignore upper two bits
1835 result |= (chars[i] & 0x3F);
1838 if (result <= 0xFFFF) {
1839 return (char) result;
1841 // We need to handle this as a UTF16 surrogate (i.e. return
1844 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1845 return (char) ((result >> 10) | 0xD800);
1849 private struct UriScheme
1851 public string scheme;
1852 public string delimiter;
1853 public int defaultPort;
1855 public UriScheme (string s, string d, int p)
1863 static UriScheme [] schemes = new UriScheme [] {
1864 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1865 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1866 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1867 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1868 new UriScheme (UriSchemeMailto, ":", 25),
1869 new UriScheme (UriSchemeNews, ":", 119),
1870 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1871 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1874 internal static string GetSchemeDelimiter (string scheme)
1876 for (int i = 0; i < schemes.Length; i++)
1877 if (schemes [i].scheme == scheme)
1878 return schemes [i].delimiter;
1879 return Uri.SchemeDelimiter;
1882 internal static int GetDefaultPort (string scheme)
1884 UriParser parser = UriParser.GetParser (scheme);
1887 return parser.DefaultPort;
1890 private string GetOpaqueWiseSchemeDelimiter ()
1895 return GetSchemeDelimiter (scheme);
1899 protected virtual bool IsBadFileSystemCharacter (char character)
1901 // It does not always overlap with InvalidPathChars.
1902 int chInt = (int) character;
1903 if (chInt < 32 || (chInt < 64 && chInt > 57))
1922 protected static bool IsExcludedCharacter (char character)
1924 if (character <= 32 || character >= 127)
1927 if (character == '"' || character == '#' || character == '%' || character == '<' ||
1928 character == '>' || character == '[' || character == '\\' || character == ']' ||
1929 character == '^' || character == '`' || character == '{' || character == '|' ||
1935 internal static bool MaybeUri (string s)
1937 int p = s.IndexOf (':');
1944 return IsPredefinedScheme (s.Substring (0, p));
1948 // Using a simple block of if's is twice as slow as the compiler generated
1949 // switch statement. But using this tuned code is faster than the
1950 // compiler generated code, with a million loops on x86-64:
1952 // With "http": .10 vs .51 (first check)
1953 // with "https": .16 vs .51 (second check)
1954 // with "foo": .22 vs .31 (never found)
1955 // with "mailto": .12 vs .51 (last check)
1958 private static bool IsPredefinedScheme (string scheme)
1960 if (scheme == null || scheme.Length < 3)
1963 char c = scheme [0];
1965 return (scheme == "http" || scheme == "https");
1967 return (scheme == "file" || scheme == "ftp");
1972 return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
1973 if (scheme == "nntp")
1977 if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
1984 protected virtual bool IsReservedCharacter (char character)
1986 if (character == '$' || character == '&' || character == '+' || character == ',' ||
1987 character == '/' || character == ':' || character == ';' || character == '=' ||
1994 private UriParser parser;
1996 private UriParser Parser {
1998 if (parser == null) {
1999 parser = UriParser.GetParser (Scheme);
2000 // no specific parser ? then use a default one
2002 parser = new DefaultUriParser ("*");
2006 set { parser = value; }
2009 public string GetComponents (UriComponents components, UriFormat format)
2011 return Parser.GetComponents (this, components, format);
2014 public bool IsBaseOf (Uri uri)
2016 #if NET_4_0 || MOONLIGHT || MOBILE
2018 throw new ArgumentNullException ("uri");
2020 return Parser.IsBaseOf (this, uri);
2023 public bool IsWellFormedOriginalString ()
2025 // funny, but it does not use the Parser's IsWellFormedOriginalString().
2026 // Also, it seems we need to *not* escape hex.
2027 return EscapeString (OriginalString, EscapeCommonBrackets) == OriginalString;
2032 private const int MaxUriLength = 32766;
2034 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
2036 if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
2037 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
2038 throw new ArgumentException ("comparisonType", msg);
2041 if ((uri1 == null) && (uri2 == null))
2044 string s1 = uri1.GetComponents (partsToCompare, compareFormat);
2045 string s2 = uri2.GetComponents (partsToCompare, compareFormat);
2046 return String.Compare (s1, s2, comparisonType);
2050 // The rules for EscapeDataString
2052 static bool NeedToEscapeDataChar (char b)
2054 return !((b >= 'A' && b <= 'Z') ||
2055 (b >= 'a' && b <= 'z') ||
2056 (b >= '0' && b <= '9') ||
2057 b == '_' || b == '~' || b == '!' || b == '\'' ||
2058 b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
2061 public static string EscapeDataString (string stringToEscape)
2063 if (stringToEscape == null)
2064 throw new ArgumentNullException ("stringToEscape");
2066 if (stringToEscape.Length > MaxUriLength) {
2067 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2068 throw new UriFormatException (msg);
2070 bool escape = false;
2071 foreach (char c in stringToEscape){
2072 if (NeedToEscapeDataChar (c)){
2078 return stringToEscape;
2081 StringBuilder sb = new StringBuilder ();
2082 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2083 foreach (byte b in bytes){
2084 if (NeedToEscapeDataChar ((char) b))
2085 sb.Append (HexEscape ((char) b));
2087 sb.Append ((char) b);
2089 return sb.ToString ();
2093 // The rules for EscapeUriString
2095 static bool NeedToEscapeUriChar (char b)
2097 return !((b >= 'A' && b <= 'Z') ||
2098 (b >= 'a' && b <= 'z') ||
2099 (b >= '&' && b <= ';') ||
2100 b == '!' || b == '#' || b == '$' || b == '=' ||
2101 b == '?' || b == '@' || b == '_' || b == '~');
2104 public static string EscapeUriString (string stringToEscape)
2106 if (stringToEscape == null)
2107 throw new ArgumentNullException ("stringToEscape");
2109 if (stringToEscape.Length > MaxUriLength) {
2110 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2111 throw new UriFormatException (msg);
2114 bool escape = false;
2115 foreach (char c in stringToEscape){
2116 if (NeedToEscapeUriChar (c)){
2122 return stringToEscape;
2124 StringBuilder sb = new StringBuilder ();
2125 byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2126 foreach (byte b in bytes){
2127 if (NeedToEscapeUriChar ((char) b))
2128 sb.Append (HexEscape ((char) b));
2130 sb.Append ((char) b);
2132 return sb.ToString ();
2135 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
2137 if (uriString == null)
2141 if (Uri.TryCreate (uriString, uriKind, out uri))
2142 return uri.IsWellFormedOriginalString ();
2146 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
2150 Uri r = new Uri (uriString, uriKind, out success);
2159 // [MonoTODO ("rework code to avoid exception catching")]
2160 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
2163 if (relativeUri == null)
2167 Uri relative = new Uri (relativeUri, UriKind.RelativeOrAbsolute);
2168 if ((baseUri != null) && baseUri.IsAbsoluteUri) {
2169 // FIXME: this should call UriParser.Resolve
2170 result = new Uri (baseUri, relative);
2171 } else if (relative.IsAbsoluteUri) {
2172 // special case - see unit tests
2175 return (result != null);
2176 } catch (UriFormatException) {
2181 //[MonoTODO ("rework code to avoid exception catching")]
2182 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
2185 if ((baseUri == null) || !baseUri.IsAbsoluteUri)
2187 #if NET_4_0 || MOONLIGHT || MOBILE
2188 if (relativeUri == null)
2192 // FIXME: this should call UriParser.Resolve
2193 result = new Uri (baseUri, relativeUri.OriginalString);
2195 } catch (UriFormatException) {
2200 public static string UnescapeDataString (string stringToUnescape)
2202 return UnescapeDataString (stringToUnescape, false);
2205 internal static string UnescapeDataString (string stringToUnescape, bool safe)
2207 if (stringToUnescape == null)
2208 throw new ArgumentNullException ("stringToUnescape");
2210 if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
2211 return stringToUnescape;
2213 StringBuilder output = new StringBuilder ();
2214 long len = stringToUnescape.Length;
2215 MemoryStream bytes = new MemoryStream ();
2218 for (int i = 0; i < len; i++) {
2219 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
2220 if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
2221 if (bytes.Length > 0) {
2222 output.Append (GetChars (bytes, Encoding.UTF8));
2223 bytes.SetLength (0);
2226 xchar = GetChar (stringToUnescape, i + 2, 4, safe);
2228 output.Append ((char) xchar);
2232 output.Append ('%');
2235 else if ((xchar = GetChar (stringToUnescape, i + 1, 2, safe)) != -1) {
2236 bytes.WriteByte ((byte) xchar);
2240 output.Append ('%');
2245 if (bytes.Length > 0) {
2246 output.Append (GetChars (bytes, Encoding.UTF8));
2247 bytes.SetLength (0);
2250 output.Append (stringToUnescape [i]);
2253 if (bytes.Length > 0) {
2254 output.Append (GetChars (bytes, Encoding.UTF8));
2258 return output.ToString ();
2261 private static int GetInt (byte b)
2264 if (c >= '0' && c <= '9')
2267 if (c >= 'a' && c <= 'f')
2268 return c - 'a' + 10;
2270 if (c >= 'A' && c <= 'F')
2271 return c - 'A' + 10;
2276 private static int GetChar (string str, int offset, int length, bool safe)
2279 int end = length + offset;
2280 for (int i = offset; i < end; i++) {
2285 int current = GetInt ((byte) c);
2288 val = (val << 4) + current;
2294 switch ((char) val) {
2301 case '&': // not documented
2308 private static char [] GetChars (MemoryStream b, Encoding e)
2310 return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2314 private void EnsureAbsoluteUri ()
2317 throw new InvalidOperationException ("This operation is not supported for a relative URI.");