linux-packaging-mono/mcs/class/System/System/UriParseComponents.cs
Xamarin Public Jenkins f3e3aab35a Imported Upstream version 4.3.2.467
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
2016-02-22 11:00:01 -05:00

567 lines
15 KiB
C#

//
// Internal UriParseComponents class
//
// Author:
// Vinicius Jarina <vinicius.jarina@xamarin.com>
//
// Copyright (C) 2012 Xamarin, Inc (http://www.xamarin.com)
//
// 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.Text;
using System.Globalization;
namespace System {
internal class ParserState
{
public ParserState (string uri, UriKind kind)
{
remaining = uri;
this.kind = kind;
elements = new UriElements ();
}
public string remaining;
public UriKind kind;
public UriElements elements;
public string error;
}
// Parse Uri components (scheme, userinfo, host, query, fragment)
// http://www.ietf.org/rfc/rfc3986.txt
internal static class UriParseComponents
{
public static UriElements ParseComponents (string uri, UriKind kind)
{
UriElements elements;
string error;
if (!TryParseComponents (uri, kind, out elements, out error))
throw new UriFormatException (error);
return elements;
}
public static bool TryParseComponents (string uri, UriKind kind, out UriElements elements, out string error)
{
uri = uri.Trim ();
ParserState state = new ParserState (uri, kind);
elements = state.elements;
error = null;
if (uri.Length == 0 && (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute)){
state.elements.isAbsoluteUri = false;
return true;
}
if (uri.Length <= 1 && kind == UriKind.Absolute) {
error = "Absolute URI is too short";
return false;
}
bool ok = ParseFilePath (state) &&
ParseScheme (state);
var scheme = state.elements.scheme;
UriParser parser = null;
if (!string.IsNullOrEmpty (scheme)) {
parser = UriParser.GetParser (scheme);
if (parser != null && !(parser is DefaultUriParser))
return true;
}
ok = ok &&
ParseAuthority (state) &&
ParsePath (state) &&
ParseQuery (state) &&
ParseFragment (state);
if (string.IsNullOrEmpty (state.elements.host) &&
(scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeGopher || scheme == Uri.UriSchemeNntp ||
scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp))
state.error = "Invalid URI: The Authority/Host could not be parsed.";
if (!string.IsNullOrEmpty (state.elements.host) &&
Uri.CheckHostName (state.elements.host) == UriHostNameType.Unknown)
state.error = "Invalid URI: The hostname could not be parsed.";
if (!string.IsNullOrEmpty (state.error)) {
elements = null;
error = state.error;
return false;
}
return true;
}
// ALPHA
private static bool IsAlpha (char ch)
{
return (('a' <= ch) && (ch <= 'z')) ||
(('A' <= ch) && (ch <= 'Z'));
}
private static bool ParseFilePath (ParserState state)
{
return ParseWindowsFilePath (state) &&
ParseWindowsUNC (state) &&
ParseUnixFilePath (state);
}
private static bool ParseWindowsFilePath (ParserState state)
{
var scheme = state.elements.scheme;
if (!string.IsNullOrEmpty (scheme) &&
scheme != Uri.UriSchemeFile && UriHelper.IsKnownScheme (scheme))
return state.remaining.Length > 0;
string part = state.remaining;
if (part.Length > 0 && (part [0] == '/' || part [0] == '\\'))
part = part.Substring (1);
if (part.Length < 2 || part [1] != ':')
return state.remaining.Length > 0;
if (!IsAlpha (part [0])) {
if (state.kind == UriKind.Absolute) {
state.error = "Invalid URI: The URI scheme is not valid.";
return false;
}
state.elements.isAbsoluteUri = false;
state.elements.path = part;
return false;
}
if (part.Length > 2 && part [2] != '\\' && part [2] != '/') {
state.error = "Relative file path is not allowed.";
return false;
}
if (string.IsNullOrEmpty (scheme)) {
state.elements.scheme = Uri.UriSchemeFile;
state.elements.delimiter = "://";
}
state.elements.path = part.Replace ("\\", "/");
return false;
}
private static bool ParseWindowsUNC (ParserState state)
{
string part = state.remaining;
if (part.Length < 2 || part [0] != '\\' || part [1] != '\\')
return state.remaining.Length > 0;
state.elements.scheme = Uri.UriSchemeFile;
state.elements.delimiter = "://";
state.elements.isUnc = true;
part = part.TrimStart ('\\');
int pos = part.IndexOf ('\\');
if (pos > 0) {
state.elements.path = part.Substring (pos);
state.elements.host = part.Substring (0, pos);
} else { // "\\\\server"
state.elements.host = part;
state.elements.path = String.Empty;
}
state.elements.path = state.elements.path.Replace ("\\", "/");
return false;
}
private static bool ParseUnixFilePath (ParserState state)
{
string part = state.remaining;
if (part.Length < 1 || part [0] != '/' || Path.DirectorySeparatorChar != '/')
return state.remaining.Length > 0;
state.elements.scheme = Uri.UriSchemeFile;
state.elements.delimiter = "://";
state.elements.isUnixFilePath = true;
state.elements.isAbsoluteUri = (state.kind == UriKind.Relative)? false : true;
if (part.Length >= 2 && part [0] == '/' && part [1] == '/') {
part = part.TrimStart (new char [] {'/'});
state.elements.path = '/' + part;
} else
state.elements.path = part;
return false;
}
// 3.1) scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
private static bool ParseScheme (ParserState state)
{
string part = state.remaining;
StringBuilder sb = new StringBuilder ();
sb.Append (part [0]);
int index;
for (index = 1; index < part.Length; index++ ) {
char ch = part [index];
if (ch != '.' && ch != '-' && ch != '+' && !IsAlpha (ch) && !Char.IsDigit (ch))
break;
sb.Append (ch);
}
if (index == 0 || index >= part.Length) {
if (state.kind == UriKind.Absolute) {
state.error = "Invalid URI: The format of the URI could not be determined.";
return false;
}
state.elements.isAbsoluteUri = false;
return state.remaining.Length > 0;
}
if (part [index] != ':') {
if (state.kind == UriKind.Absolute) {
state.error = "Invalid URI: The URI scheme is not valid.";
return false;
}
state.elements.isAbsoluteUri = false;
return state.remaining.Length > 0;
}
state.elements.scheme = sb.ToString ().ToLowerInvariant ();
state.remaining = part.Substring (index);
// Check scheme name characters as specified in RFC2396.
// Note: different checks in 1.x and 2.0
if (!Uri.CheckSchemeName (state.elements.scheme)) {
if (state.kind == UriKind.Absolute) {
state.error = "Invalid URI: The URI scheme is not valid.";
return false;
}
state.elements.isAbsoluteUri = false;
return state.remaining.Length > 0;
}
return ParseDelimiter (state);
}
private static bool ParseDelimiter (ParserState state)
{
var delimiter = Uri.GetSchemeDelimiter (state.elements.scheme);
if (!state.remaining.StartsWith (delimiter, StringComparison.Ordinal)) {
if (UriHelper.IsKnownScheme (state.elements.scheme)) {
state.error = "Invalid URI: The Authority/Host could not be parsed.";
return false;
}
delimiter = ":";
}
state.elements.delimiter = delimiter;
state.remaining = state.remaining.Substring (delimiter.Length);
return state.remaining.Length > 0;
}
private static bool ParseAuthority (ParserState state)
{
if (state.elements.delimiter != Uri.SchemeDelimiter && state.elements.scheme != Uri.UriSchemeMailto)
return state.remaining.Length > 0;
return ParseUser (state) &&
ParseHost (state) &&
ParsePort (state);
}
static bool IsUnreserved (char ch)
{
return ch == '-' || ch == '.' || ch == '_' || ch == '~';
}
static bool IsSubDelim (char ch)
{
return ch == '!' || ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' ||
ch == '*' || ch == '+' || ch == ',' || ch == ';' || ch == '=';
}
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
private static bool ParseUser (ParserState state)
{
string part = state.remaining;
StringBuilder sb = null;
int index;
for (index = 0; index < part.Length; index++) {
char ch = part [index];
bool isEscapedChar = false;
var oldIndex = index;
// Spaces should be percentage encoded #31172
if (ch == ' ') {
if (sb == null)
sb = new StringBuilder ();
sb.Append ("%20");
continue;
}
if (ch == '%'){
if (!Uri.IsHexEncoding (part, index))
return false;
ch = Uri.HexUnescape (part, ref index);
index--;
isEscapedChar = true;
}
if (!Char.IsLetterOrDigit (ch) && !IsUnreserved (ch) && !IsSubDelim (ch) && ch != ':'){
if (!isEscapedChar)
break;
ch = '%';
index = oldIndex;
}
if (sb == null)
sb = new StringBuilder ();
sb.Append (ch);
}
if (index + 1 <= part.Length && part [index] == '@') {
if (state.elements.scheme == Uri.UriSchemeFile) {
state.error = "Invalid URI: The hostname could not be parsed.";
return false;
}
state.elements.user = sb == null ? "" : sb.ToString ();
state.remaining = state.remaining.Substring (index + 1);
}
return state.remaining.Length > 0;
}
// host = IP-literal / IPv4address / reg-name
private static bool ParseHost (ParserState state)
{
string part = state.remaining;
if (state.elements.scheme == Uri.UriSchemeFile && part.Length >= 2 &&
(part [0] == '\\' || part [0] == '/') && part [1] == part [0]) {
part = part.TrimStart (part [0]);
state.remaining = part;
}
if (!ParseWindowsFilePath (state))
return false;
StringBuilder sb = new StringBuilder ();
var tmpHost = "";
var possibleIpv6 = false;
int index;
for (index = 0; index < part.Length; index++) {
char ch = part [index];
if (ch == '/' || ch == '#' || ch == '?')
break;
// Possible IPv6
if (string.IsNullOrEmpty (tmpHost) && ch == ':') {
tmpHost = sb.ToString ();
possibleIpv6 = true;
}
sb.Append (ch);
if (possibleIpv6 && ch == ']')
break;
}
if (possibleIpv6) {
IPv6Address ipv6addr;
if (IPv6Address.TryParse (sb.ToString (), out ipv6addr)) {
var ipStr = ipv6addr.ToString (false);
//remove scope
ipStr = ipStr.Split ('%') [0];
state.elements.host = "[" + ipStr + "]";
state.elements.scopeId = ipv6addr.ScopeId;
state.remaining = part.Substring (sb.Length);
return state.remaining.Length > 0;
}
state.elements.host = tmpHost;
} else
state.elements.host = sb.ToString ();
state.elements.host = state.elements.host.ToLowerInvariant ();
state.remaining = part.Substring (state.elements.host.Length);
if (state.elements.scheme == Uri.UriSchemeFile &&
state.elements.host != "") {
// under Windows all file://host URI are considered UNC, which is not the case other MacOS (e.g. Silverlight)
#if BOOTSTRAP_BASIC
state.elements.isUnc = (Path.DirectorySeparatorChar == '\\');
#else
state.elements.isUnc = Environment.IsRunningOnWindows;
#endif
}
return state.remaining.Length > 0;
}
// port = *DIGIT
private static bool ParsePort (ParserState state)
{
string part = state.remaining;
if (part.Length == 0 || part [0] != ':')
return part.Length > 0;
StringBuilder sb = new StringBuilder ();
int index;
for (index = 1; index < part.Length; index++ ) {
char ch = part [index];
if (!char.IsDigit (ch)) {
if (ch == '/' || ch == '#' || ch == '?')
break;
state.error = "Invalid URI: Invalid port specified.";
return false;
}
sb.Append (ch);
}
if (index <= part.Length)
state.remaining = part.Substring (index);
if (sb.Length == 0)
return state.remaining.Length > 0;
int port;
if (!Int32.TryParse (sb.ToString (), NumberStyles.None, CultureInfo.InvariantCulture, out port) ||
port < 0 || port > UInt16.MaxValue) {
state.error = "Invalid URI: Invalid port number";
return false;
}
state.elements.port = port;
return state.remaining.Length > 0;
}
private static bool ParsePath (ParserState state)
{
string part = state.remaining;
StringBuilder sb = new StringBuilder ();
int index;
for (index = 0; index < part.Length; index++) {
char ch = part [index];
var supportsQuery = UriHelper.SupportsQuery (state.elements.scheme);
if (ch == '#' || (supportsQuery && ch == '?'))
break;
sb.Append (ch);
}
if (index <= part.Length)
state.remaining = part.Substring (index);
state.elements.path = sb.ToString ();
return state.remaining.Length > 0;
}
private static bool ParseQuery (ParserState state)
{
string part = state.remaining;
if (!UriHelper.SupportsQuery (state.elements.scheme))
return part.Length > 0;
if (part.Length == 0 || part [0] != '?')
return part.Length > 0;
StringBuilder sb = new StringBuilder ();
int index;
for (index = 1; index < part.Length; index++) {
char ch = part [index];
if (ch == '#')
break;
sb.Append (ch);
}
if (index <= part.Length)
state.remaining = part.Substring (index);
state.elements.query = sb.ToString ();
return state.remaining.Length > 0;
}
private static bool ParseFragment (ParserState state)
{
string part = state.remaining;
if (part.Length == 0 || part [0] != '#')
return part.Length > 0;
StringBuilder sb = new StringBuilder ();
int index;
for (index = 1; index < part.Length; index++) {
char ch = part [index];
sb.Append (ch);
}
state.elements.fragment = sb.ToString ();
return false;
}
}
}