// (C) 2003 Novell inc.
//
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Collections;
+using System.Globalization;
// See RFC 2396 for more info on URI's.
// o scheme excludes the scheme delimiter
// o port is -1 to indicate no port is defined
// o path is empty or starts with / when scheme delimiter == "://"
- // o query is empty or starts with ? char
- // o fragment is empty or starts with # char
+ // o query is empty or starts with ? char, escaped.
+ // o fragment is empty or starts with # char, unescaped.
// 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 source;
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 bool userEscaped = false;
private string cachedAbsoluteUri = null;
private string cachedToString = null;
+ private string cachedLocalPath = null;
private int cachedHashCode = 0;
private static readonly string hexUpperChars = "0123456789ABCDEF";
}
public Uri (string uriString, bool dontEscape)
- {
+ {
userEscaped = dontEscape;
- Parse (uriString);
-
- if (userEscaped)
- return;
-
- host = EscapeString (host, false, true, false);
- path = EscapeString (path);
+ source = uriString;
+ Parse ();
}
public Uri (Uri baseUri, string relativeUri)
userEscaped = dontEscape;
- 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 == null)
throw new NullReferenceException ("relativeUri");
- if (relativeUri == String.Empty) {
- this.path = baseUri.path;
- this.query = baseUri.query;
- this.fragment = baseUri.fragment;
+ // Check Windows UNC (for // it is scheme/host separator)
+ if (relativeUri.StartsWith ("\\\\")) {
+ source = relativeUri;
+ Parse ();
return;
}
int pos = relativeUri.IndexOf (':');
if (pos != -1) {
- int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\'});
+ int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
- if (pos2 > pos) {
+ // pos2 < 0 ... e.g. mailto
+ // pos2 > pos ... to block ':' in query part
+ if (pos2 > pos || pos2 < 0) {
// equivalent to new Uri (relativeUri, dontEscape)
- Parse (relativeUri);
+ source = relativeUri;
+ Parse ();
- if (userEscaped)
- return;
-
- host = EscapeString (host, false, true, false);
- path = EscapeString (path);
return;
}
}
+ this.scheme = baseUri.scheme;
+ this.host = baseUri.host;
+ this.port = baseUri.port;
+ this.userinfo = baseUri.userinfo;
+ this.isUnc = baseUri.isUnc;
+ 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
+ // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
pos = relativeUri.IndexOf ('#');
if (pos != -1) {
fragment = relativeUri.Substring (pos);
+ // fragment is not escaped.
relativeUri = relativeUri.Substring (0, pos);
}
}
if (relativeUri.Length > 0 && relativeUri [0] == '/') {
- path = relativeUri;
- if (!userEscaped)
- path = EscapeString (path);
- return;
+ if (relativeUri.Length > 1 && relativeUri [1] == '/') {
+ source = scheme + ':' + relativeUri;
+ Parse ();
+ return;
+ } else {
+ path = relativeUri;
+ if (!userEscaped)
+ path = EscapeString (path);
+ return;
+ }
}
// par 5.2 step 6 a)
public string AbsoluteUri {
get {
- if (cachedAbsoluteUri == null)
+ if (cachedAbsoluteUri == null) {
cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
+ }
return cachedAbsoluteUri;
}
}
return true;
try {
- return IPAddress.IsLoopback (IPAddress.Parse (host));
+ if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
+ return true;
} catch (FormatException) {}
try {
public string LocalPath {
get {
+ if (cachedLocalPath != null)
+ return cachedLocalPath;
if (!IsFile)
- return path;
+ return AbsolutePath;
+
+ bool windows = (path.Length > 3 && path [1] == ':' &&
+ (path [2] == '\\' || path [2] == '/'));
+
if (!IsUnc) {
string p = Unescape (path);
- if (System.IO.Path.DirectorySeparatorChar == '\\')
- return p.Replace ('/', '\\');
+ if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
+ cachedLocalPath = p.Replace ('/', '\\');
else
- return p;
+ cachedLocalPath = p;
+ } else {
+ // support *nix and W32 styles
+ if (path.Length > 1 && path [1] == ':')
+ cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
+
+ // LAMESPEC: ok, now we cannot determine
+ // if such URI like "file://foo/bar" is
+ // Windows UNC or unix file path, so
+ // they should be handled differently.
+ else if (System.IO.Path.DirectorySeparatorChar == '\\')
+ cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
+ else
+ cachedLocalPath = Unescape (path);
}
-
- // 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 (is_root_path? "/": "") + (is_wins_dir? "/": "") + Unescape (host + path);
+ if (cachedLocalPath == String.Empty)
+ cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
+ return cachedLocalPath;
}
}
return false;
for (int i = 0; i < 4; i++) {
try {
- int d = Int32.Parse (captures [i]);
+ int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
if (d < 0 || d > 255)
return false;
} catch (Exception) {
return false;
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));
+
+ CultureInfo inv = CultureInfo.InvariantCulture;
+ return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
+ (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
+ (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
+ (this.port == uri.port) &&
+ (this.path == uri.path) &&
+ (this.query.ToLower (inv) == uri.query.ToLower (inv)));
}
public override int GetHashCode ()
throw new ArgumentException ("pattern");
if (index < 0 || index >= pattern.Length)
- throw new ArgumentOutOfRangeException ("index");
-
- if (((index + 3) > pattern.Length) ||
- (pattern [index] != '%') ||
- !IsHexDigit (pattern [index + 1]) ||
- !IsHexDigit (pattern [index + 2]))
- {
- return pattern[index++];
- }
+ throw new ArgumentOutOfRangeException ("index");
+
+ int stage = 0;
+ int c = 0;
+ do {
+ if (((index + 3) > pattern.Length) ||
+ (pattern [index] != '%') ||
+ !IsHexDigit (pattern [index + 1]) ||
+ !IsHexDigit (pattern [index + 2]))
+ {
+ if (stage == 0)
+ return pattern [index++];
+ break;
+ }
+
+ index++;
+ int msb = FromHex (pattern [index++]);
+ int lsb = FromHex (pattern [index++]);
+ int b = (msb << 4) + lsb;
+
+ if (stage == 0) {
+ if (b < 0xc0)
+ return (char) b;
+ else if (b < 0xE0) {
+ c = b - 0xc0;
+ stage = 2;
+ } else if (b < 0xF0) {
+ c = b - 0xe0;
+ stage = 3;
+ } else if (b < 0xF8) {
+ c = b - 0xf0;
+ stage = 4;
+ } else if (b < 0xFB) {
+ c = b - 0xf8;
+ stage = 5;
+ } else if (b < 0xFE) {
+ c = b - 0xfc;
+ stage = 6;
+ }
+ c <<= (stage - 1) * 6;
+ }
+ else
+ c += (b - 0x80) << ((stage - 1) * 6);
+//Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
+ stage--;
+ } while (stage > 0);
- index++;
- return (char) ((FromHex (pattern [index++]) << 4) + FromHex (pattern [index++]));
+ return (char) c;
}
public static bool IsHexDigit (char digit)
{
if (cachedToString != null)
return cachedToString;
- if (IsFile && !IsUnc)
- cachedToString = Unescape (AbsoluteUri);
- else
- cachedToString = AbsoluteUri;
-
+ string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
+ cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
return cachedToString;
}
(escapeHex && (c == '#')) ||
(escapeBrackets && (c == '[' || c == ']')) ||
(escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
- // FIXME: EscapeString should allow wide characters (> 255). HexEscape rejects it.
s.Append (HexEscape (c));
continue;
}
return s.ToString ();
}
- [MonoTODO ("Find out what this should do!")]
+ // This method is called from .ctor(). When overriden, we can
+ // avoid the "absolute uri" constraints of the .ctor() by
+ // overriding with custom code.
protected virtual void Parse ()
{
- }
+ Parse (source);
+
+ if (userEscaped)
+ return;
+
+ host = EscapeString (host, false, true, false);
+ path = EscapeString (path);
+ }
+
+ protected virtual string Unescape (string str)
+ {
+ return Unescape (str, false);
+ }
- protected virtual string Unescape (string str)
+ private string Unescape (string str, bool excludeSharp)
{
if (str == null)
return String.Empty;
for (int i = 0; i < len; i++) {
char c = str [i];
if (c == '%') {
- s.Append (HexUnescape (str, ref i));
+ char x = HexUnescape (str, ref i);
+ if (excludeSharp && x == '#')
+ s.Append ("%23");
+ else
+ s.Append (x);
i--;
} else
- s.Append (c);
+ s.Append (c);
}
return s.ToString ();
}
// Private Methods
+ private void ParseAsWindowsUNC (string uriString)
+ {
+ scheme = UriSchemeFile;
+ port = -1;
+ fragment = String.Empty;
+ query = String.Empty;
+ isUnc = true;
+
+ uriString = uriString.TrimStart (new char [] {'\\'});
+ int pos = uriString.IndexOf ('\\');
+ if (pos > 0) {
+ path = uriString.Substring (pos);
+ host = uriString.Substring (0, pos);
+ } else { // "\\\\server"
+ host = uriString;
+ path = String.Empty;
+ }
+ path = path.Replace ("\\", "/");
+ }
+
+ private void ParseAsWindowsAbsoluteFilePath (string uriString)
+ {
+ if (uriString.Length > 2 && uriString [2] != '\\'
+ && uriString [2] != '/')
+ throw new UriFormatException ("Relative file path is not allowed.");
+ scheme = UriSchemeFile;
+ host = String.Empty;
+ port = -1;
+ path = uriString.Replace ("\\", "/");
+ fragment = String.Empty;
+ query = String.Empty;
+ }
+
+ private void ParseAsUnixAbsoluteFilePath (string uriString)
+ {
+ isUnixFilePath = true;
+ scheme = UriSchemeFile;
+ port = -1;
+ fragment = String.Empty;
+ query = String.Empty;
+ host = String.Empty;
+ path = null;
+
+ if (uriString.StartsWith ("//")) {
+ uriString = uriString.TrimStart (new char [] {'/'});
+ // Now we don't regard //foo/bar as "foo" host.
+ /*
+ int pos = uriString.IndexOf ('/');
+ if (pos > 0) {
+ path = '/' + uriString.Substring (pos + 1);
+ host = uriString.Substring (0, pos);
+ } else { // "///server"
+ host = uriString;
+ path = String.Empty;
+ }
+ */
+ path = '/' + uriString;
+ }
+ if (path == null)
+ path = uriString;
+ }
+
// this parse method is as relaxed as possible about the format
// it will hardly ever throw a UriFormatException
private void Parse (string uriString)
if (len <= 1)
throw new UriFormatException ();
- // 1
- char c = 'x';
int pos = 0;
- for (; pos < len; pos++) {
- c = uriString [pos];
- if ((c == ':') || (c == '/') || (c == '\\') || (c == '?') || (c == '#'))
- break;
- }
-
- if (pos == len)
- throw new UriFormatException ("The format of the URI could not be determined.");
- if (c == '/') {
- is_root_path = true;
+ // 1, 2
+ // Identify Windows path, unix path, or standard URI.
+ pos = uriString.IndexOf (':');
+ if (pos < 0) {
+ // It must be Unix file path or Windows UNC
+ if (uriString [0] == '/')
+ ParseAsUnixAbsoluteFilePath (uriString);
+ else if (uriString.StartsWith ("\\\\"))
+ ParseAsWindowsUNC (uriString);
+ else
+ throw new UriFormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
+ return;
}
-
- if ((uriString.Length >= 2) &&
- ((uriString [0] == '/') && (uriString [1] == '/')) ||
- ((uriString [0] == '\\') || (uriString [1] == '\\'))) {
- is_wins_dir = true;
- is_root_path = true;
+ else if (pos == 1) {
+ if (!Char.IsLetter (uriString [0]))
+ throw new UriFormatException ("URI scheme must start with alphabet character.");
+ // This means 'a:' == windows full path.
+ ParseAsWindowsAbsoluteFilePath (uriString);
+ return;
}
- // 2 scheme
- if (c == ':') {
- if (pos == 1) {
- // a windows filepath
- 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 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;
+ // scheme
+ scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
+ // Check scheme name characters as specified in RFC2396.
+ if (!Char.IsLetter (scheme [0]))
+ throw new UriFormatException ("URI scheme must start with alphabet character.");
+ for (int i = 1; i < scheme.Length; i++) {
+ if (!Char.IsLetterOrDigit (scheme, i)) {
+ switch (scheme [i]) {
+ case '+':
+ case '-':
+ case '.':
+ break;
+ default:
+ throw new UriFormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
+ }
}
- if (uriString.Length > 8 && uriString [8] != '/')
- isUnc = true;
- }
-
- // 3
- if ((uriString.Length >= 2) &&
- ((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;
}
+ uriString = uriString.Substring (pos + 1);
// 8 fragment
pos = uriString.IndexOf ('#');
if (pos != -1) {
query = uriString.Substring (pos);
uriString = uriString.Substring (0, pos);
+ if (!userEscaped)
+ query = EscapeString (query);
+ }
+
+ // 3
+ bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
+ if (uriString.StartsWith ("//")) {
+ if (uriString.StartsWith ("////"))
+ unixAbsPath = false;
+ uriString = uriString.TrimStart (new char [] {'/'});
+ if (uriString.Length > 1 && uriString [1] == ':')
+ unixAbsPath = false;
+ } else if (!IsPredefinedScheme (scheme)) {
+ path = uriString;
+ isOpaquePart = true;
+ return;
}
// 5 path
- pos = uriString.IndexOfAny (new char[] {'/', '\\'});
+ pos = uriString.IndexOfAny (new char[] {'/'});
+ if (unixAbsPath)
+ pos = -1;
if (pos == -1) {
if ((scheme != Uri.UriSchemeMailto) &&
(scheme != Uri.UriSchemeNews) &&
(scheme != Uri.UriSchemeFile))
path = "/";
} else {
- path = uriString.Substring (pos).Replace ('\\', '/');
+ path = uriString.Substring (pos);
uriString = uriString.Substring (0, pos);
}
// 4.b port
port = -1;
pos = uriString.LastIndexOf (":");
+ if (unixAbsPath)
+ pos = -1;
if (pos != -1 && pos != (uriString.Length - 1)) {
string portStr = uriString.Remove (0, pos + 1);
if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
try {
- port = (int) UInt32.Parse (portStr);
+ port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
uriString = uriString.Substring (0, pos);
} catch (Exception) {
throw new UriFormatException ("Invalid URI: invalid port number");
}
}
- if (host.Length == 2 && host [1] == ':') {
+ if (unixAbsPath) {
+ path = '/' + uriString;
+ host = String.Empty;
+ } else if (host.Length == 2 && host [1] == ':') {
// windows filepath
path = host + path;
host = String.Empty;
path = Reduce (path);
}
- public static string Reduce (string path)
+ private static string Reduce (string path)
{
path = path.Replace ('\\','/');
string [] parts = path.Split ('/');
return "/";
result.Insert (0, "");
- return String.Join ("/", (string []) result.ToArray (typeof (string)));
+
+ string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
+ if (path.EndsWith ("/"))
+ res += '/';
+
+ return res;
}
private struct UriScheme