//
// System.Uri
//
-// Author:
+// Authors:
// Lawrence Pit (loz@cable.a2000.nl)
-//
-// Author:
// Garrett Rooney (rooneg@electricjellyfish.net)
+// Ian MacLean (ianm@activestate.com)
+// Ben Maurer (bmaurer@users.sourceforge.net)
+// Atsushi Enomoto (atsushi@ximian.com)
//
// (C) 2001 Garrett Rooney
+// (C) 2003 Ian MacLean
+// (C) 2003 Ben Maurer
+// (C) 2003 Novell inc.
//
using System.Net;
using System.Runtime.Serialization;
using System.Text;
+using System.Collections;
// See RFC 2396 for more info on URI's.
// o fragment is empty or starts with # char
// o all class variables are in escaped format when they are escapable,
// except cachedToString.
-
+ // o UNC is supported, as starts with "\\" for windows,
+ // or "//" with unix.
+
+ private bool isWindowsFilePath = false;
+ private bool isUnixFilePath = false;
private string scheme = String.Empty;
private string host = String.Empty;
private int port = -1;
private string query = String.Empty;
private string fragment = String.Empty;
private string userinfo = String.Empty;
+ private bool is_root_path = false;
+ private bool is_wins_dir = true;
+ private bool isUnc = false;
+ private bool isOpaquePart = false;
private string [] segments;
if (userEscaped)
return;
-
+
host = EscapeString (host, false, true, false);
path = EscapeString (path);
- query = EscapeString (query);
- fragment = EscapeString (fragment, false, false, true);
}
public Uri (Uri baseUri, string relativeUri)
public Uri (Uri baseUri, string relativeUri, bool dontEscape)
{
+ if (baseUri == null)
+ throw new NullReferenceException ("baseUri");
+
// See RFC 2396 Par 5.2 and Appendix C
userEscaped = dontEscape;
- this.scheme = baseUri.scheme;
- this.host = baseUri.host;
- this.port = baseUri.port;
- this.userinfo = baseUri.userinfo;
-
if (relativeUri == null)
throw new NullReferenceException ("relativeUri");
- if (relativeUri == String.Empty) {
- this.path = baseUri.path;
- this.query = baseUri.query;
- this.fragment = baseUri.fragment;
- return;
- }
-
int pos = relativeUri.IndexOf (':');
if (pos != -1) {
+
int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\'});
+
if (pos2 > pos) {
// equivalent to new Uri (relativeUri, dontEscape)
Parse (relativeUri);
host = EscapeString (host, false, true, false);
path = EscapeString (path);
- query = EscapeString (query);
- fragment = EscapeString (fragment, false, false, true);
return;
}
}
-
+
+ this.scheme = baseUri.scheme;
+ this.host = baseUri.host;
+ this.port = baseUri.port;
+ this.userinfo = baseUri.userinfo;
+ this.isUnc = baseUri.isUnc;
+ this.isWindowsFilePath = baseUri.isWindowsFilePath;
+ this.isUnixFilePath = baseUri.isUnixFilePath;
+ this.isOpaquePart = baseUri.isOpaquePart;
+
+ if (relativeUri == String.Empty) {
+ this.path = baseUri.path;
+ this.query = baseUri.query;
+ this.fragment = baseUri.fragment;
+ return;
+ }
+
// 8 fragment
pos = relativeUri.IndexOf ('#');
if (pos != -1) {
fragment = relativeUri.Substring (pos);
- if (!userEscaped)
- fragment = EscapeString (fragment, false, false, true);
relativeUri = relativeUri.Substring (0, pos);
}
relativeUri = relativeUri.Substring (0, pos);
}
- if (relativeUri[0] == '/') {
+ if (relativeUri.Length > 0 && relativeUri [0] == '/') {
path = relativeUri;
if (!userEscaped)
path = EscapeString (path);
// par 5.2 step 6 a)
path = baseUri.path;
- pos = path.LastIndexOf ('/');
- if (pos > 0)
- path = path.Substring (0, pos + 1);
-
+ if (relativeUri.Length > 0 || query.Length > 0) {
+ pos = path.LastIndexOf ('/');
+ if (pos >= 0)
+ path = path.Substring (0, pos + 1);
+ }
+
+ if(relativeUri.Length == 0)
+ return;
+
// 6 b)
path += relativeUri;
-
+
// 6 c)
int startIndex = 0;
while (true) {
// 6 f)
if (path.Length > 3 && path.EndsWith ("/..")) {
pos = path.LastIndexOf ('/', path.Length - 4);
- Console.WriteLine ("6f " + pos);
if (pos != -1)
if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
path = path.Remove (pos + 1, path.Length - pos - 1);
}
// Properties
-
+
public string AbsolutePath {
get { return path; }
}
public string AbsoluteUri {
- get {
- if (cachedAbsoluteUri == null)
- cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
+ get {
+ if (cachedAbsoluteUri == null) {
+// cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
+ string qf = IsFile ? query + fragment : EscapeString (query + fragment);
+ cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + qf;
+ }
return cachedAbsoluteUri;
}
}
}
public UriHostNameType HostNameType {
- get { return CheckHostName (host); }
+ get {
+ UriHostNameType ret = CheckHostName (host);
+ if (ret != UriHostNameType.Unknown)
+ return ret;
+
+ // looks it always returns Basic...
+ return UriHostNameType.Basic; //.Unknown;
+ }
}
public bool IsDefaultPort {
return true;
try {
- return IPAddress.IsLoopback (IPAddress.Parse (host));
+ if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
+ return true;
} catch (FormatException) {}
try {
}
}
- public bool IsUnc {
- get { return (scheme == Uri.UriSchemeFile); }
+ public bool IsUnc {
+ // rule: This should be true only if
+ // - uri string starts from "\\", or
+ // - uri string starts from "//" (Samba way)
+ get { return isUnc; }
}
public string LocalPath {
- get {
- if (!IsUnc)
- return path;
-
+ get {
+ if (!IsFile)
+ return path;
+
+ bool windows = (path.Length > 3 && path [1] == ':' &&
+ (path [2] == '\\' || path [2] == '/'));
+
+ if (!IsUnc) {
+ string p = Unescape (path);
+ if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
+ return p.Replace ('/', '\\');
+ else
+ return p;
+ }
+
// support *nix and W32 styles
if (path.Length > 1 && path [1] == ':')
return Unescape (path.Replace ('/', '\\'));
-
+
if (System.IO.Path.DirectorySeparatorChar == '\\')
return "\\\\" + Unescape (host + path.Replace ('/', '\\'));
else
- return "/" + Unescape (host + path);
+ return (is_root_path? "/": "") + (is_wins_dir? "/": "") + Unescape (host + path);
}
}
return true;
}
+ [MonoTODO ("Find out what this should do")]
+ protected virtual void Canonicalize ()
+ {
+ }
+
public static bool CheckSchemeName (string schemeName)
{
if (schemeName == null || schemeName.Length == 0)
return true;
}
-
+
+ [MonoTODO ("Find out what this should do")]
+ protected virtual void CheckSecurity ()
+ {
+ }
+
public override bool Equals (object comparant)
{
if (comparant == null)
uri = new Uri (s);
}
- return ((this.scheme == uri.scheme) &&
- (this.userinfo == uri.userinfo) &&
- (this.host == uri.host) &&
- (this.port == uri.port) &&
- (this.path == uri.path) &&
- (this.query == uri.query));
+ return ((this.scheme.ToLower () == uri.scheme.ToLower ()) &&
+ (this.userinfo.ToLower () == uri.userinfo.ToLower ()) &&
+ (this.host.ToLower () == uri.host.ToLower ()) &&
+ (this.port == uri.port) &&
+ (this.path == uri.path) &&
+ (this.query.ToLower () == uri.query.ToLower ()));
}
public override int GetHashCode ()
int defaultPort;
switch (part) {
case UriPartial.Scheme :
- return scheme + GetSchemeDelimiter (scheme);
+ return scheme + GetOpaqueWiseSchemeDelimiter ();
case UriPartial.Authority :
if (host == String.Empty ||
scheme == Uri.UriSchemeMailto ||
StringBuilder s = new StringBuilder ();
s.Append (scheme);
- s.Append (GetSchemeDelimiter (scheme));
- if (path.Length > 1 && path [1] == ':' && "file".Equals (scheme))
+ s.Append (GetOpaqueWiseSchemeDelimiter ());
+ if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
s.Append ('/'); // win32 file
if (userinfo.Length > 0)
s.Append (userinfo).Append ('@');
case UriPartial.Path :
StringBuilder sb = new StringBuilder ();
sb.Append (scheme);
- sb.Append (GetSchemeDelimiter (scheme));
- if (path.Length > 1 && path [1] == ':' && "file".Equals (scheme))
+ sb.Append (GetOpaqueWiseSchemeDelimiter ());
+ if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
sb.Append ('/'); // win32 file
if (userinfo.Length > 0)
sb.Append (userinfo).Append ('@');
{
if (cachedToString != null)
return cachedToString;
-
cachedToString = Unescape (AbsoluteUri);
-
+
return cachedToString;
}
- public void GetObjectData (SerializationInfo info,
+ void ISerializable.GetObjectData (SerializationInfo info,
StreamingContext context)
{
info.AddValue ("AbsoluteUri", this.AbsoluteUri);
// Internal Methods
+ protected virtual void Escape ()
+ {
+ path = EscapeString (path);
+ }
+
protected static string EscapeString (string str)
{
return EscapeString (str, false, true, true);
if (str == null)
return String.Empty;
+ byte [] data = Encoding.UTF8.GetBytes (str.ToCharArray ());
StringBuilder s = new StringBuilder ();
- int len = str.Length;
+ int len = data.Length;
for (int i = 0; i < len; i++) {
- char c = str [i];
+ char c = (char) data [i];
// reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
// mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
// control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
// delims = "<" | ">" | "#" | "%" | <">
// unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
+ // check for escape code already placed in str,
+ // i.e. for encoding that follows the pattern
+ // "%hexhex" in a string, where "hex" is a digit from 0-9
+ // or a letter from A-F (case-insensitive).
+ if('%'.Equals(c) && IsHexEncoding(str,i))
+ {
+ // if ,yes , copy it as is
+ s.Append(c);
+ s.Append(str[++i]);
+ s.Append(str[++i]);
+ continue;
+ }
+
if ((c <= 0x20) || (c >= 0x7f) ||
("<>%\"{}|\\^`".IndexOf (c) != -1) ||
(escapeHex && (c == '#')) ||
(escapeBrackets && (c == '[' || c == ']')) ||
(escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
+ // FIXME: EscapeString should allow wide characters (> 255). HexEscape rejects it.
s.Append (HexEscape (c));
continue;
}
+
s.Append (c);
}
return s.ToString ();
- }
+ }
+
+ [MonoTODO ("Find out what this should do!")]
+ protected virtual void Parse ()
+ {
+ }
protected virtual string Unescape (string str)
{
s.Append (HexUnescape (str, ref i));
i--;
} else
- s.Append (c);
+ s.Append (c);
}
return s.ToString ();
}
break;
}
+ if (pos == len)
+ throw new UriFormatException ("The format of the URI could not be determined.");
+
+ if (c == '/') {
+ is_root_path = true;
+ }
+
+ if ((uriString.Length >= 2) &&
+ ((uriString [0] == '/') && (uriString [1] == '/')) ||
+ ((uriString [0] == '\\') || (uriString [1] == '\\'))) {
+ is_wins_dir = true;
+ is_root_path = true;
+ }
+
// 2 scheme
- if (c == ':') {
+ if (c == ':') {
if (pos == 1) {
// a windows filepath
- scheme = "file";
+ if (uriString.Length < 3 || (uriString [2] != '\\' && uriString [2] != '/'))
+ throw new UriFormatException ("Invalid URI: The format of the URI could not be determined.");
+ isWindowsFilePath = true;
+ scheme = Uri.UriSchemeFile;
path = uriString.Replace ('\\', '/');
return;
}
-
+
scheme = uriString.Substring (0, pos).ToLower ();
uriString = uriString.Remove (0, pos + 1);
- } else
- scheme = "file";
-
+ } else if ((c == '/') && (pos == 0)) {
+ scheme = Uri.UriSchemeFile;
+ if (uriString.Length > 1 && uriString [1] != '/')
+ // unix bare filepath
+ isUnixFilePath = true;
+ else
+ // unix UNC (kind of)
+ isUnc = true;
+ } else {
+ if (uriString [0] != '\\' && uriString [0] != '/' && !uriString.StartsWith ("file://"))
+ throw new UriFormatException ("Invalid URI: The format of the URI could not be determined.");
+ scheme = Uri.UriSchemeFile;
+ if (uriString.StartsWith ("\\\\")) {
+ isUnc = true;
+ isWindowsFilePath = true;
+ }
+ if (uriString.Length > 8 && uriString [8] != '/')
+ isUnc = true;
+ }
+
// 3
if ((uriString.Length >= 2) &&
- ((uriString [0] == '/') || (uriString [0] == '\\')) &&
- ((uriString [1] == '/') || (uriString [1] == '\\')))
- if ("file".Equals (scheme))
- uriString = uriString.TrimStart (new char [] {'/', '\\'});
+ ((uriString [0] == '/') && (uriString [1] == '/')) ||
+ ((uriString [0] == '\\') || (uriString [1] == '\\'))) {
+ if (scheme == Uri.UriSchemeFile)
+ uriString = uriString.TrimStart (new char [] {'/', '\\'});
else
uriString = uriString.Remove (0, 2);
-
+ } else if (!IsPredefinedScheme (scheme)) {
+ path = uriString;
+ isOpaquePart = true;
+ return;
+ }
+
// 8 fragment
pos = uriString.IndexOf ('#');
- if (pos != -1) {
+ if (!IsUnc && pos != -1) {
fragment = uriString.Substring (pos);
uriString = uriString.Substring (0, pos);
}
pos = uriString.IndexOfAny (new char[] {'/', '\\'});
if (pos == -1) {
if ((scheme != Uri.UriSchemeMailto) &&
- (scheme != Uri.UriSchemeNews))
+ (scheme != Uri.UriSchemeNews) &&
+ (scheme != Uri.UriSchemeFile))
path = "/";
} else {
path = uriString.Substring (pos).Replace ('\\', '/');
string portStr = uriString.Remove (0, pos + 1);
if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
try {
- port = Int32.Parse (portStr);
- new System.Net.IPEndPoint (0, port); // test validity port
+ port = (int) UInt32.Parse (portStr);
uriString = uriString.Substring (0, pos);
} catch (Exception) {
throw new UriFormatException ("Invalid URI: invalid port number");
// 4 authority
host = uriString;
- if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']')
+ if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
try {
host = "[" + IPv6Address.Parse (host).ToString () + "]";
} catch (Exception) {
throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
}
+ }
+
if (host.Length == 2 && host [1] == ':') {
// windows filepath
path = host + path;
host = String.Empty;
+ } else if (isUnixFilePath) {
+ uriString = "//" + uriString;
+ host = String.Empty;
} else if (host.Length == 0) {
throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
+ } else if (scheme == UriSchemeFile) {
+ isUnc = true;
}
+
+ if ((scheme != Uri.UriSchemeMailto) &&
+ (scheme != Uri.UriSchemeNews) &&
+ (scheme != Uri.UriSchemeFile))
+ path = Reduce (path);
}
+ private static string Reduce (string path)
+ {
+ path = path.Replace ('\\','/');
+ string [] parts = path.Split ('/');
+ ArrayList result = new ArrayList ();
+
+ int end = parts.Length;
+ for (int i = 0; i < end; i++) {
+ string current = parts [i];
+ if (current == "" || current == "." )
+ continue;
+
+ if (current == "..") {
+ if (result.Count == 0) {
+ if (i == 1) // see bug 52599
+ continue;
+ throw new Exception ("Invalid path.");
+ }
+
+ result.RemoveAt (result.Count - 1);
+ continue;
+ }
+
+ result.Add (current);
+ }
+
+ if (result.Count == 0)
+ return "/";
+
+ result.Insert (0, "");
+
+ string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
+ if (path.EndsWith ("/"))
+ res += '/';
+
+ return res;
+ }
private struct UriScheme
{
static UriScheme [] schemes = new UriScheme [] {
new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
- new UriScheme (UriSchemeHttps, SchemeDelimiter, 223),
- new UriScheme (UriSchemeFtp, SchemeDelimiter, 23),
+ new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
+ new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
new UriScheme (UriSchemeMailto, ":", 25),
new UriScheme (UriSchemeNews, ":", -1),
return -1;
}
+ private string GetOpaqueWiseSchemeDelimiter ()
+ {
+ if (isOpaquePart)
+ return ":";
+ else
+ return GetSchemeDelimiter (scheme);
+ }
+
protected virtual bool IsBadFileSystemCharacter (char ch)
{
- foreach (char c in System.IO.Path.InvalidPathChars)
- if (c == ch)
- return true;
+ // It does not always overlap with InvalidPathChars.
+ int chInt = (int) ch;
+ if (chInt < 32 || (chInt < 64 && chInt > 57))
+ return true;
+ switch (chInt) {
+ case 0:
+ case 34: // "
+ case 38: // &
+ case 42: // *
+ case 44: // ,
+ case 47: // /
+ case 92: // \
+ case 94: // ^
+ case 124: // |
+ return true;
+ }
+
return false;
}
return false;
}
+ private static bool IsPredefinedScheme (string scheme)
+ {
+ switch (scheme) {
+ case "http":
+ case "https":
+ case "file":
+ case "ftp":
+ case "nntp":
+ case "gopher":
+ case "mailto":
+ case "news":
+ return true;
+ default:
+ return false;
+ }
+ }
+
protected virtual bool IsReservedCharacter (char ch)
{
if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||