| Index: sdk/lib/core/uri.dart
|
| diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
|
| index b43b3ad0474ac9f63d089f80ef196831297e2993..5718ca80b83b870dd578011592707b4c7d99cdc4 100644
|
| --- a/sdk/lib/core/uri.dart
|
| +++ b/sdk/lib/core/uri.dart
|
| @@ -4,24 +4,6 @@
|
|
|
| part of dart.core;
|
|
|
| -// Frequently used character codes.
|
| -const int _SPACE = 0x20;
|
| -const int _PERCENT = 0x25;
|
| -const int _PLUS = 0x2B;
|
| -const int _DOT = 0x2E;
|
| -const int _SLASH = 0x2F;
|
| -const int _COLON = 0x3A;
|
| -const int _UPPER_CASE_A = 0x41;
|
| -const int _UPPER_CASE_Z = 0x5A;
|
| -const int _LEFT_BRACKET = 0x5B;
|
| -const int _BACKSLASH = 0x5C;
|
| -const int _RIGHT_BRACKET = 0x5D;
|
| -const int _LOWER_CASE_A = 0x61;
|
| -const int _LOWER_CASE_F = 0x66;
|
| -const int _LOWER_CASE_Z = 0x7A;
|
| -
|
| -const String _hexDigits = "0123456789ABCDEF";
|
| -
|
| /**
|
| * A parsed URI, such as a URL.
|
| *
|
| @@ -33,17 +15,77 @@ const String _hexDigits = "0123456789ABCDEF";
|
| * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
|
| * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html
|
| */
|
| -abstract class Uri {
|
| +class Uri {
|
| /**
|
| - * Returns the natural base URI for the current platform.
|
| + * The scheme component of the URI.
|
| *
|
| - * When running in a browser this is the current URL of the current page
|
| - * (from `window.location.href`).
|
| + * Returns the empty string if there is no scheme component.
|
| *
|
| - * When not running in a browser this is the file URI referencing
|
| - * the current working directory.
|
| + * A URI scheme is case insensitive.
|
| + * The returned scheme is canonicalized to lowercase letters.
|
| */
|
| - external static Uri get base;
|
| + // We represent the missing scheme as an empty string.
|
| + // A valid scheme cannot be empty.
|
| + final String scheme;
|
| +
|
| + /**
|
| + * The user-info part of the authority.
|
| + *
|
| + * Does not distinguish between an empty user-info and an absent one.
|
| + * The value is always non-null.
|
| + * Is considered absent if [_host] is `null`.
|
| + */
|
| + final String _userInfo;
|
| +
|
| + /**
|
| + * The host name of the URI.
|
| + *
|
| + * Set to `null` if there is no authority in the URI.
|
| + * The host name is the only mandatory part of an authority, so we use
|
| + * it to mark whether an authority part was present or not.
|
| + */
|
| + final String _host;
|
| +
|
| + /**
|
| + * The port number part of the authority.
|
| + *
|
| + * The port. Set to null if there is no port. Normalized to null if
|
| + * the port is the default port for the scheme.
|
| + */
|
| + int _port;
|
| +
|
| + /**
|
| + * The path of the URI.
|
| + *
|
| + * Always non-null.
|
| + */
|
| + String _path;
|
| +
|
| + // The query content, or null if there is no query.
|
| + final String _query;
|
| +
|
| + // The fragment content, or null if there is no fragment.
|
| + final String _fragment;
|
| +
|
| + /**
|
| + * Cache the computed return value of [pathSegements].
|
| + */
|
| + List<String> _pathSegments;
|
| +
|
| + /**
|
| + * Cache the computed return value of [queryParameters].
|
| + */
|
| + Map<String, String> _queryParameters;
|
| + Map<String, List<String>> _queryParameterLists;
|
| +
|
| + /// Internal non-verifying constructor. Only call with validated arguments.
|
| + Uri._internal(this.scheme,
|
| + this._userInfo,
|
| + this._host,
|
| + this._port,
|
| + this._path,
|
| + this._query,
|
| + this._fragment);
|
|
|
| /**
|
| * Creates a new URI from its components.
|
| @@ -116,15 +158,39 @@ abstract class Uri {
|
| * general delimiters, are escaped if necessary.
|
| * If `fragment` is omitted or `null`, the URI has no fragment part.
|
| */
|
| - factory Uri({String scheme,
|
| - String userInfo,
|
| + factory Uri({String scheme : "",
|
| + String userInfo : "",
|
| String host,
|
| int port,
|
| String path,
|
| Iterable<String> pathSegments,
|
| String query,
|
| Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
|
| - String fragment}) = _Uri;
|
| + String fragment}) {
|
| + scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
|
| + userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
|
| + host = _makeHost(host, 0, _stringOrNullLength(host), false);
|
| + // Special case this constructor for backwards compatibility.
|
| + if (query == "") query = null;
|
| + query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
|
| + fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
|
| + port = _makePort(port, scheme);
|
| + bool isFile = (scheme == "file");
|
| + if (host == null &&
|
| + (userInfo.isNotEmpty || port != null || isFile)) {
|
| + host = "";
|
| + }
|
| + bool hasAuthority = (host != null);
|
| + path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
|
| + scheme, hasAuthority);
|
| + if (scheme.isEmpty && host == null && !path.startsWith('/')) {
|
| + path = _normalizeRelativePath(path);
|
| + } else {
|
| + path = _removeDotSegments(path);
|
| + }
|
| + return new Uri._internal(scheme, userInfo, host, port,
|
| + path, query, fragment);
|
| + }
|
|
|
| /**
|
| * Creates a new `http` URI from authority, path and query.
|
| @@ -161,7 +227,9 @@ abstract class Uri {
|
| */
|
| factory Uri.http(String authority,
|
| String unencodedPath,
|
| - [Map<String, String> queryParameters]) = _Uri.http;
|
| + [Map<String, String> queryParameters]) {
|
| + return _makeHttpUri("http", authority, unencodedPath, queryParameters);
|
| + }
|
|
|
| /**
|
| * Creates a new `https` URI from authority, path and query.
|
| @@ -171,542 +239,103 @@ abstract class Uri {
|
| */
|
| factory Uri.https(String authority,
|
| String unencodedPath,
|
| - [Map<String, String> queryParameters]) = _Uri.https;
|
| + [Map<String, String> queryParameters]) {
|
| + return _makeHttpUri("https", authority, unencodedPath, queryParameters);
|
| + }
|
|
|
| /**
|
| - * Creates a new file URI from an absolute or relative file path.
|
| - *
|
| - * The file path is passed in [path].
|
| - *
|
| - * This path is interpreted using either Windows or non-Windows
|
| - * semantics.
|
| - *
|
| - * With non-Windows semantics the slash ("/") is used to separate
|
| - * path segments.
|
| - *
|
| - * With Windows semantics, backslash ("\") and forward-slash ("/")
|
| - * are used to separate path segments, except if the path starts
|
| - * with "\\?\" in which case, only backslash ("\") separates path
|
| - * segments.
|
| - *
|
| - * If the path starts with a path separator an absolute URI is
|
| - * created. Otherwise a relative URI is created. One exception from
|
| - * this rule is that when Windows semantics is used and the path
|
| - * starts with a drive letter followed by a colon (":") and a
|
| - * path separator then an absolute URI is created.
|
| - *
|
| - * The default for whether to use Windows or non-Windows semantics
|
| - * determined from the platform Dart is running on. When running in
|
| - * the standalone VM this is detected by the VM based on the
|
| - * operating system. When running in a browser non-Windows semantics
|
| - * is always used.
|
| - *
|
| - * To override the automatic detection of which semantics to use pass
|
| - * a value for [windows]. Passing `true` will use Windows
|
| - * semantics and passing `false` will use non-Windows semantics.
|
| - *
|
| - * Examples using non-Windows semantics:
|
| - *
|
| - * ```
|
| - * // xxx/yyy
|
| - * new Uri.file("xxx/yyy", windows: false);
|
| - *
|
| - * // xxx/yyy/
|
| - * new Uri.file("xxx/yyy/", windows: false);
|
| - *
|
| - * // file:///xxx/yyy
|
| - * new Uri.file("/xxx/yyy", windows: false);
|
| - *
|
| - * // file:///xxx/yyy/
|
| - * new Uri.file("/xxx/yyy/", windows: false);
|
| - *
|
| - * // C:
|
| - * new Uri.file("C:", windows: false);
|
| - * ```
|
| - *
|
| - * Examples using Windows semantics:
|
| + * Returns the authority component.
|
| *
|
| - * ```
|
| - * // xxx/yyy
|
| - * new Uri.file(r"xxx\yyy", windows: true);
|
| + * The authority is formatted from the [userInfo], [host] and [port]
|
| + * parts.
|
| *
|
| - * // xxx/yyy/
|
| - * new Uri.file(r"xxx\yyy\", windows: true);
|
| + * Returns the empty string if there is no authority component.
|
| + */
|
| + String get authority {
|
| + if (!hasAuthority) return "";
|
| + var sb = new StringBuffer();
|
| + _writeAuthority(sb);
|
| + return sb.toString();
|
| + }
|
| +
|
| + /**
|
| + * Returns the user info part of the authority component.
|
| *
|
| - * file:///xxx/yyy
|
| - * new Uri.file(r"\xxx\yyy", windows: true);
|
| + * Returns the empty string if there is no user info in the
|
| + * authority component.
|
| + */
|
| + String get userInfo => _userInfo;
|
| +
|
| + /**
|
| + * Returns the host part of the authority component.
|
| *
|
| - * file:///xxx/yyy/
|
| - * new Uri.file(r"\xxx\yyy/", windows: true);
|
| + * Returns the empty string if there is no authority component and
|
| + * hence no host.
|
| *
|
| - * // file:///C:/xxx/yyy
|
| - * new Uri.file(r"C:\xxx\yyy", windows: true);
|
| + * If the host is an IP version 6 address, the surrounding `[` and `]` is
|
| + * removed.
|
| *
|
| - * // This throws an error. A path with a drive letter is not absolute.
|
| - * new Uri.file(r"C:", windows: true);
|
| + * The host string is case-insensitive.
|
| + * The returned host name is canonicalized to lower-case
|
| + * with upper-case percent-escapes.
|
| + */
|
| + String get host {
|
| + if (_host == null) return "";
|
| + if (_host.startsWith('[')) {
|
| + return _host.substring(1, _host.length - 1);
|
| + }
|
| + return _host;
|
| + }
|
| +
|
| + /**
|
| + * Returns the port part of the authority component.
|
| *
|
| - * // This throws an error. A path with a drive letter is not absolute.
|
| - * new Uri.file(r"C:xxx\yyy", windows: true);
|
| + * Returns the defualt port if there is no port number in the authority
|
| + * component. That's 80 for http, 443 for https, and 0 for everything else.
|
| + */
|
| + int get port {
|
| + if (_port == null) return _defaultPort(scheme);
|
| + return _port;
|
| + }
|
| +
|
| + // The default port for the scheme of this Uri..
|
| + static int _defaultPort(String scheme) {
|
| + if (scheme == "http") return 80;
|
| + if (scheme == "https") return 443;
|
| + return 0;
|
| + }
|
| +
|
| + /**
|
| + * Returns the path component.
|
| *
|
| - * // file://server/share/file
|
| - * new Uri.file(r"\\server\share\file", windows: true);
|
| - * ```
|
| + * The returned path is encoded. To get direct access to the decoded
|
| + * path use [pathSegments].
|
| *
|
| - * If the path passed is not a legal file path [ArgumentError] is thrown.
|
| + * Returns the empty string if there is no path component.
|
| */
|
| - factory Uri.file(String path, {bool windows}) = _Uri.file;
|
| + String get path => _path;
|
|
|
| /**
|
| - * Like [Uri.file] except that a non-empty URI path ends in a slash.
|
| + * Returns the query component. The returned query is encoded. To get
|
| + * direct access to the decoded query use [queryParameters].
|
| *
|
| - * If [path] is not empty, and it doesn't end in a directory separator,
|
| - * then a slash is added to the returned URI's path.
|
| - * In all other cases, the result is the same as returned by `Uri.file`.
|
| + * Returns the empty string if there is no query component.
|
| */
|
| - factory Uri.directory(String path, {bool windows}) = _Uri.directory;
|
| + String get query => (_query == null) ? "" : _query;
|
|
|
| /**
|
| - * Creates a `data:` URI containing the [content] string.
|
| - *
|
| - * Converts the content to a bytes using [encoding] or the charset specified
|
| - * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
|
| - * then encodes the bytes into the resulting data URI.
|
| + * Returns the fragment identifier component.
|
| *
|
| - * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
|
| - * bytes is replaced by a percent encoding). If [base64] is true, the bytes
|
| - * are instead encoded using [BASE64].
|
| + * Returns the empty string if there is no fragment identifier
|
| + * component.
|
| + */
|
| + String get fragment => (_fragment == null) ? "" : _fragment;
|
| +
|
| + /**
|
| + * Creates a new `Uri` object by parsing a URI string.
|
| *
|
| - * If [encoding] is not provided and [parameters] has a `charset` entry,
|
| - * that name is looked up using [Encoding.getByName],
|
| - * and if the lookup returns an encoding, that encoding is used to convert
|
| - * [content] to bytes.
|
| - * If providing both an [encoding] and a charset [parameter], they should
|
| - * agree, otherwise decoding won't be able to use the charset parameter
|
| - * to determine the encoding.
|
| - *
|
| - * If [mimeType] and/or [parameters] are supplied, they are added to the
|
| - * created URI. If any of these contain characters that are not allowed
|
| - * in the data URI, the character is percent-escaped. If the character is
|
| - * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
|
| - * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
|
| - * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
|
| - *
|
| - * To read the content back, use [UriData.contentAsString].
|
| - */
|
| - factory Uri.dataFromString(String content,
|
| - {String mimeType,
|
| - Encoding encoding,
|
| - Map<String, String> parameters,
|
| - bool base64: false}) {
|
| - UriData data = new UriData.fromString(content,
|
| - mimeType: mimeType,
|
| - encoding: encoding,
|
| - parameters: parameters,
|
| - base64: base64);
|
| - return data.uri;
|
| - }
|
| -
|
| - /**
|
| - * Creates a `data:` URI containing an encoding of [bytes].
|
| - *
|
| - * Defaults to Base64 encoding the bytes, but if [percentEncoded]
|
| - * is `true`, the bytes will instead be percent encoded (any non-ASCII
|
| - * or non-valid-ASCII-character byte is replaced by a percent encoding).
|
| - *
|
| - * To read the bytes back, use [UriData.contentAsBytes].
|
| - *
|
| - * It defaults to having the mime-type `application/octet-stream`.
|
| - * The [mimeType] and [parameters] are added to the created URI.
|
| - * If any of these contain characters that are not allowed
|
| - * in the data URI, the character is percent-escaped. If the character is
|
| - * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
|
| - * encoded.
|
| - */
|
| - factory Uri.dataFromBytes(List<int> bytes,
|
| - {mimeType: "application/octet-stream",
|
| - Map<String, String> parameters,
|
| - percentEncoded: false}) {
|
| - UriData data = new UriData.fromBytes(bytes,
|
| - mimeType: mimeType,
|
| - parameters: parameters,
|
| - percentEncoded: percentEncoded);
|
| - return data.uri;
|
| - }
|
| -
|
| - /**
|
| - * The scheme component of the URI.
|
| - *
|
| - * Returns the empty string if there is no scheme component.
|
| - *
|
| - * A URI scheme is case insensitive.
|
| - * The returned scheme is canonicalized to lowercase letters.
|
| - */
|
| - String get scheme;
|
| -
|
| - /**
|
| - * Returns the authority component.
|
| - *
|
| - * The authority is formatted from the [userInfo], [host] and [port]
|
| - * parts.
|
| - *
|
| - * Returns the empty string if there is no authority component.
|
| - */
|
| - String get authority;
|
| -
|
| - /**
|
| - * Returns the user info part of the authority component.
|
| - *
|
| - * Returns the empty string if there is no user info in the
|
| - * authority component.
|
| - */
|
| - String get userInfo;
|
| -
|
| - /**
|
| - * Returns the host part of the authority component.
|
| - *
|
| - * Returns the empty string if there is no authority component and
|
| - * hence no host.
|
| - *
|
| - * If the host is an IP version 6 address, the surrounding `[` and `]` is
|
| - * removed.
|
| - *
|
| - * The host string is case-insensitive.
|
| - * The returned host name is canonicalized to lower-case
|
| - * with upper-case percent-escapes.
|
| - */
|
| - String get host;
|
| -
|
| - /**
|
| - * Returns the port part of the authority component.
|
| - *
|
| - * Returns the defualt port if there is no port number in the authority
|
| - * component. That's 80 for http, 443 for https, and 0 for everything else.
|
| - */
|
| - int get port;
|
| -
|
| - /**
|
| - * Returns the path component.
|
| - *
|
| - * The returned path is encoded. To get direct access to the decoded
|
| - * path use [pathSegments].
|
| - *
|
| - * Returns the empty string if there is no path component.
|
| - */
|
| - String get path;
|
| -
|
| - /**
|
| - * Returns the query component. The returned query is encoded. To get
|
| - * direct access to the decoded query use [queryParameters].
|
| - *
|
| - * Returns the empty string if there is no query component.
|
| - */
|
| - String get query;
|
| -
|
| - /**
|
| - * Returns the fragment identifier component.
|
| - *
|
| - * Returns the empty string if there is no fragment identifier
|
| - * component.
|
| - */
|
| - String get fragment;
|
| -
|
| - /**
|
| - * Returns the URI path split into its segments. Each of the segments in the
|
| - * returned list have been decoded. If the path is empty the empty list will
|
| - * be returned. A leading slash `/` does not affect the segments returned.
|
| - *
|
| - * The returned list is unmodifiable and will throw [UnsupportedError] on any
|
| - * calls that would mutate it.
|
| - */
|
| - List<String> get pathSegments;
|
| -
|
| - /**
|
| - * Returns the URI query split into a map according to the rules
|
| - * specified for FORM post in the [HTML 4.01 specification section
|
| - * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| - * Each key and value in the returned map has been decoded.
|
| - * If there is no query the empty map is returned.
|
| - *
|
| - * Keys in the query string that have no value are mapped to the
|
| - * empty string.
|
| - * If a key occurs more than once in the query string, it is mapped to
|
| - * an arbitrary choice of possible value.
|
| - * The [queryParametersAll] getter can provide a map
|
| - * that maps keys to all of their values.
|
| - *
|
| - * The returned map is unmodifiable.
|
| - */
|
| - Map<String, String> get queryParameters;
|
| -
|
| - /**
|
| - * Returns the URI query split into a map according to the rules
|
| - * specified for FORM post in the [HTML 4.01 specification section
|
| - * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| - * Each key and value in the returned map has been decoded. If there is no
|
| - * query the empty map is returned.
|
| - *
|
| - * Keys are mapped to lists of their values. If a key occurs only once,
|
| - * its value is a singleton list. If a key occurs with no value, the
|
| - * empty string is used as the value for that occurrence.
|
| - *
|
| - * The returned map and the lists it contains are unmodifiable.
|
| - */
|
| - Map<String, List<String>> get queryParametersAll;
|
| -
|
| - /**
|
| - * Returns whether the URI is absolute.
|
| - *
|
| - * A URI is an absolute URI in the sense of RFC 3986 if it has a scheme
|
| - * and no fragment.
|
| - */
|
| - bool get isAbsolute;
|
| -
|
| - /**
|
| - * Returns whether the URI has a [scheme] component.
|
| - */
|
| - bool get hasScheme => scheme.isNotEmpty;
|
| -
|
| - /**
|
| - * Returns whether the URI has an [authority] component.
|
| - */
|
| - bool get hasAuthority;
|
| -
|
| - /**
|
| - * Returns whether the URI has an explicit port.
|
| - *
|
| - * If the port number is the default port number
|
| - * (zero for unrecognized schemes, with http (80) and https (443) being
|
| - * recognized),
|
| - * then the port is made implicit and omitted from the URI.
|
| - */
|
| - bool get hasPort;
|
| -
|
| - /**
|
| - * Returns whether the URI has a query part.
|
| - */
|
| - bool get hasQuery;
|
| -
|
| - /**
|
| - * Returns whether the URI has a fragment part.
|
| - */
|
| - bool get hasFragment;
|
| -
|
| - /**
|
| - * Returns whether the URI has an empty path.
|
| - */
|
| - bool get hasEmptyPath;
|
| -
|
| - /**
|
| - * Returns whether the URI has an absolute path (starting with '/').
|
| - */
|
| - bool get hasAbsolutePath;
|
| -
|
| - /**
|
| - * Returns the origin of the URI in the form scheme://host:port for the
|
| - * schemes http and https.
|
| - *
|
| - * It is an error if the scheme is not "http" or "https".
|
| - *
|
| - * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
|
| - */
|
| - String get origin;
|
| -
|
| - /**
|
| - * Returns the file path from a file URI.
|
| - *
|
| - * The returned path has either Windows or non-Windows
|
| - * semantics.
|
| - *
|
| - * For non-Windows semantics the slash ("/") is used to separate
|
| - * path segments.
|
| - *
|
| - * For Windows semantics the backslash ("\") separator is used to
|
| - * separate path segments.
|
| - *
|
| - * If the URI is absolute the path starts with a path separator
|
| - * unless Windows semantics is used and the first path segment is a
|
| - * drive letter. When Windows semantics is used a host component in
|
| - * the uri in interpreted as a file server and a UNC path is
|
| - * returned.
|
| - *
|
| - * The default for whether to use Windows or non-Windows semantics
|
| - * determined from the platform Dart is running on. When running in
|
| - * the standalone VM this is detected by the VM based on the
|
| - * operating system. When running in a browser non-Windows semantics
|
| - * is always used.
|
| - *
|
| - * To override the automatic detection of which semantics to use pass
|
| - * a value for [windows]. Passing `true` will use Windows
|
| - * semantics and passing `false` will use non-Windows semantics.
|
| - *
|
| - * If the URI ends with a slash (i.e. the last path component is
|
| - * empty) the returned file path will also end with a slash.
|
| - *
|
| - * With Windows semantics URIs starting with a drive letter cannot
|
| - * be relative to the current drive on the designated drive. That is
|
| - * for the URI `file:///c:abc` calling `toFilePath` will throw as a
|
| - * path segment cannot contain colon on Windows.
|
| - *
|
| - * Examples using non-Windows semantics (resulting of calling
|
| - * toFilePath in comment):
|
| - *
|
| - * Uri.parse("xxx/yyy"); // xxx/yyy
|
| - * Uri.parse("xxx/yyy/"); // xxx/yyy/
|
| - * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
|
| - * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
|
| - * Uri.parse("file:///C:"); // /C:
|
| - * Uri.parse("file:///C:a"); // /C:a
|
| - *
|
| - * Examples using Windows semantics (resulting URI in comment):
|
| - *
|
| - * Uri.parse("xxx/yyy"); // xxx\yyy
|
| - * Uri.parse("xxx/yyy/"); // xxx\yyy\
|
| - * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
|
| - * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
|
| - * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
|
| - * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
|
| - * // cannot contain colon on Windows.
|
| - * Uri.parse("file://server/share/file"); // \\server\share\file
|
| - *
|
| - * If the URI is not a file URI calling this throws
|
| - * [UnsupportedError].
|
| - *
|
| - * If the URI cannot be converted to a file path calling this throws
|
| - * [UnsupportedError].
|
| - */
|
| - // TODO(lrn): Deprecate and move functionality to File class or similar.
|
| - // The core libraries should not worry about the platform.
|
| - String toFilePath({bool windows});
|
| -
|
| - /**
|
| - * Access the structure of a `data:` URI.
|
| - *
|
| - * Returns a [UriData] object for `data:` URIs and `null` for all other
|
| - * URIs.
|
| - * The [UriData] object can be used to access the media type and data
|
| - * of a `data:` URI.
|
| - */
|
| - UriData get data;
|
| -
|
| - /// Returns a hash code computed as `toString().hashCode`.
|
| - ///
|
| - /// This guarantees that URIs with the same normalized
|
| - int get hashCode;
|
| -
|
| - /// A URI is equal to another URI with the same normalized representation.
|
| - bool operator==(Object other);
|
| -
|
| - /// Returns the normalized string representation of the URI.
|
| - String toString();
|
| -
|
| - /**
|
| - * Returns a new `Uri` based on this one, but with some parts replaced.
|
| - *
|
| - * This method takes the same parameters as the [new Uri] constructor,
|
| - * and they have the same meaning.
|
| - *
|
| - * At most one of [path] and [pathSegments] must be provided.
|
| - * Likewise, at most one of [query] and [queryParameters] must be provided.
|
| - *
|
| - * Each part that is not provided will default to the corresponding
|
| - * value from this `Uri` instead.
|
| - *
|
| - * This method is different from [Uri.resolve] which overrides in a
|
| - * hierarchial manner,
|
| - * and can instead replace each part of a `Uri` individually.
|
| - *
|
| - * Example:
|
| - *
|
| - * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
|
| - * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
|
| - * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
|
| - *
|
| - * This method acts similarly to using the `new Uri` constructor with
|
| - * some of the arguments taken from this `Uri` . Example:
|
| - *
|
| - * Uri uri3 = new Uri(
|
| - * scheme: "A",
|
| - * userInfo: uri1.userInfo,
|
| - * host: uri1.host,
|
| - * port: uri1.port,
|
| - * path: "D/E/E",
|
| - * query: uri1.query,
|
| - * fragment: "G");
|
| - * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
|
| - * print(uri2 == uri3); // prints true.
|
| - *
|
| - * Using this method can be seen as a shorthand for the `Uri` constructor
|
| - * call above, but may also be slightly faster because the parts taken
|
| - * from this `Uri` need not be checked for validity again.
|
| - */
|
| - Uri replace({String scheme,
|
| - String userInfo,
|
| - String host,
|
| - int port,
|
| - String path,
|
| - Iterable<String> pathSegments,
|
| - String query,
|
| - Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
|
| - String fragment});
|
| -
|
| - /**
|
| - * Returns a `Uri` that differs from this only in not having a fragment.
|
| - *
|
| - * If this `Uri` does not have a fragment, it is itself returned.
|
| - */
|
| - Uri removeFragment();
|
| -
|
| - /**
|
| - * Resolve [reference] as an URI relative to `this`.
|
| - *
|
| - * First turn [reference] into a URI using [Uri.parse]. Then resolve the
|
| - * resulting URI relative to `this`.
|
| - *
|
| - * Returns the resolved URI.
|
| - *
|
| - * See [resolveUri] for details.
|
| - */
|
| - Uri resolve(String reference);
|
| -
|
| - /**
|
| - * Resolve [reference] as an URI relative to `this`.
|
| - *
|
| - * Returns the resolved URI.
|
| - *
|
| - * The algorithm "Transform Reference" for resolving a reference is described
|
| - * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
|
| - *
|
| - * Updated to handle the case where the base URI is just a relative path -
|
| - * that is: when it has no scheme or authority and the path does not start
|
| - * with a slash.
|
| - * In that case, the paths are combined without removing leading "..", and
|
| - * an empty path is not converted to "/".
|
| - */
|
| - Uri resolveUri(Uri reference);
|
| -
|
| - /**
|
| - * Returns a URI where the path has been normalized.
|
| - *
|
| - * A normalized path does not contain `.` segments or non-leading `..`
|
| - * segments.
|
| - * Only a relative path with no scheme or authority may contain
|
| - * leading `..` segments,
|
| - * a path that starts with `/` will also drop any leading `..` segments.
|
| - *
|
| - * This uses the same normalization strategy as `new Uri().resolve(this)`.
|
| - *
|
| - * Does not change any part of the URI except the path.
|
| - *
|
| - * The default implementation of `Uri` always normalizes paths, so calling
|
| - * this function has no effect.
|
| - */
|
| - Uri normalizePath();
|
| -
|
| - /**
|
| - * Creates a new `Uri` object by parsing a URI string.
|
| - *
|
| - * If [start] and [end] are provided, only the substring from `start`
|
| - * to `end` is parsed as a URI.
|
| + * If [start] and [end] are provided, only the substring from `start`
|
| + * to `end` is parsed as a URI.
|
| *
|
| * If the string is not valid as a URI or URI reference,
|
| * a [FormatException] is thrown.
|
| @@ -764,769 +393,207 @@ abstract class Uri {
|
| //
|
| // query = *( pchar / "/" / "?" )
|
| //
|
| - // fragment = *( pchar / "/" / "?" )
|
| - end ??= uri.length;
|
| -
|
| - // Special case data:URIs. Ignore case when testing.
|
| - if (end >= start + 5) {
|
| - int dataDelta = _startsWithData(uri, start);
|
| - if (dataDelta == 0) {
|
| - // The case is right.
|
| - if (start > 0 || end < uri.length) uri = uri.substring(start, end);
|
| - return UriData._parse(uri, 5, null).uri;
|
| - } else if (dataDelta == 0x20) {
|
| - return UriData._parse(uri.substring(start + 5, end), 0, null).uri;
|
| - }
|
| - // Otherwise the URI doesn't start with "data:" or any case variant of it.
|
| - }
|
| -
|
| - // The following index-normalization belongs with the scanning, but is
|
| - // easier to do here because we already have extracted variables from the
|
| - // indices list.
|
| - var indices = new List<int>(8);//new List<int>.filled(8, start - 1);
|
| -
|
| - // Set default values for each position.
|
| - // The value will either be correct in some cases where it isn't set
|
| - // by the scanner, or it is clearly recognizable as an unset value.
|
| - indices
|
| - ..[0] = 0
|
| - ..[_schemeEndIndex] = start - 1
|
| - ..[_hostStartIndex] = start - 1
|
| - ..[_notSimpleIndex] = start - 1
|
| - ..[_portStartIndex] = start
|
| - ..[_pathStartIndex] = start
|
| - ..[_queryStartIndex] = end
|
| - ..[_fragmentStartIndex] = end;
|
| - var state = _scan(uri, start, end, _uriStart, indices);
|
| - // Some states that should be non-simple, but the URI ended early.
|
| - // Paths that end at a ".." must be normalized to end in "../".
|
| - if (state >= _nonSimpleEndStates) {
|
| - indices[_notSimpleIndex] = end;
|
| - }
|
| - int schemeEnd = indices[_schemeEndIndex];
|
| - if (schemeEnd >= start) {
|
| - // Rescan the scheme part now that we know it's not a path.
|
| - state = _scan(uri, start, schemeEnd, _schemeStart, indices);
|
| - if (state == _schemeStart) {
|
| - // Empty scheme.
|
| - indices[_notSimpleIndex] = schemeEnd;
|
| - }
|
| - }
|
| - // The returned positions are limited by the scanners ability to write only
|
| - // one position per character, and only the current position.
|
| - // Scanning from left to right, we only know whether something is a scheme
|
| - // or a path when we see a `:` or `/`, and likewise we only know if the first
|
| - // `/` is part of the path or is leading an authority component when we see
|
| - // the next character.
|
| -
|
| - int hostStart = indices[_hostStartIndex] + 1;
|
| - int portStart = indices[_portStartIndex];
|
| - int pathStart = indices[_pathStartIndex];
|
| - int queryStart = indices[_queryStartIndex];
|
| - int fragmentStart = indices[_fragmentStartIndex];
|
| -
|
| - // We may discover scheme while handling special cases.
|
| - String scheme;
|
| -
|
| - // Derive some positions that weren't set to normalize the indices.
|
| - // If pathStart isn't set (it's before scheme end or host start), then
|
| - // the path is empty.
|
| - if (fragmentStart < queryStart) queryStart = fragmentStart;
|
| - if (pathStart < hostStart || pathStart <= schemeEnd) {
|
| - pathStart = queryStart;
|
| - }
|
| - // If there is an authority with no port, set the port position
|
| - // to be at the end of the authority (equal to pathStart).
|
| - // This also handles a ":" in a user-info component incorrectly setting
|
| - // the port start position.
|
| - if (portStart < hostStart) portStart = pathStart;
|
| -
|
| - assert(hostStart == start || schemeEnd <= hostStart);
|
| - assert(hostStart <= portStart);
|
| - assert(schemeEnd <= pathStart);
|
| - assert(portStart <= pathStart);
|
| - assert(pathStart <= queryStart);
|
| - assert(queryStart <= fragmentStart);
|
| -
|
| - bool isSimple = indices[_notSimpleIndex] < start;
|
| -
|
| - if (isSimple) {
|
| - // Check/do normalizations that weren't detected by the scanner.
|
| - // This includes removal of empty port or userInfo,
|
| - // or scheme specific port and path normalizations.
|
| - if (hostStart > schemeEnd + 3) {
|
| - // Always be non-simple if URI contains user-info.
|
| - // The scanner doesn't set the not-simple position in this case because
|
| - // it's setting the host-start position instead.
|
| - isSimple = false;
|
| - } else if (portStart > start && portStart + 1 == pathStart) {
|
| - // If the port is empty, it should be omitted.
|
| - // Pathological case, don't bother correcting it.
|
| - isSimple = false;
|
| - } else if (queryStart < end &&
|
| - (queryStart == pathStart + 2 &&
|
| - uri.startsWith("..", pathStart)) ||
|
| - (queryStart > pathStart + 2 &&
|
| - uri.startsWith("/..", queryStart - 3))) {
|
| - // The path ends in a ".." segment. This should be normalized to "../".
|
| - // We didn't detect this while scanning because a query or fragment was
|
| - // detected at the same time (which is why we only need to check this
|
| - // if there is something after the path).
|
| - isSimple = false;
|
| - } else {
|
| - // There are a few scheme-based normalizations that
|
| - // the scanner couldn't check.
|
| - // That means that the input is very close to simple, so just do
|
| - // the normalizations.
|
| - if (schemeEnd == start + 4) {
|
| - // Do scheme based normalizations for file, http.
|
| - if (uri.startsWith("file", start)) {
|
| - scheme = "file";
|
| - if (hostStart <= start) {
|
| - // File URIs should have an authority.
|
| - // Paths after an authority should be absolute.
|
| - String schemeAuth = "file://";
|
| - int delta = 2;
|
| - if (!uri.startsWith("/", pathStart)) {
|
| - schemeAuth = "file:///";
|
| - delta = 3;
|
| - }
|
| - uri = schemeAuth + uri.substring(pathStart, end);
|
| - schemeEnd -= start;
|
| - hostStart = 7;
|
| - portStart = 7;
|
| - pathStart = 7;
|
| - queryStart += delta - start;
|
| - fragmentStart += delta - start;
|
| - start = 0;
|
| - end = uri.length;
|
| - } else if (pathStart == queryStart) {
|
| - // Uri has authority and empty path. Add "/" as path.
|
| - if (start == 0 && end == uri.length) {
|
| - uri = uri.replaceRange(pathStart, queryStart, "/");
|
| - queryStart += 1;
|
| - fragmentStart += 1;
|
| - end += 1;
|
| - } else {
|
| - uri = "${uri.substring(start, pathStart)}/"
|
| - "${uri.substring(queryStart, end)}";
|
| - schemeEnd -= start;
|
| - hostStart -= start;
|
| - portStart -= start;
|
| - pathStart -= start;
|
| - queryStart += 1 - start;
|
| - fragmentStart += 1 - start;
|
| - start = 0;
|
| - end = uri.length;
|
| - }
|
| - }
|
| - } else if (uri.startsWith("http", start)) {
|
| - scheme = "http";
|
| - // HTTP URIs should not have an explicit port of 80.
|
| - if (portStart > start && portStart + 3 == pathStart &&
|
| - uri.startsWith("80", portStart + 1)) {
|
| - if (start == 0 && end == uri.length) {
|
| - uri = uri.replaceRange(portStart, pathStart, "");
|
| - pathStart -= 3;
|
| - queryStart -= 3;
|
| - fragmentStart -= 3;
|
| - end -= 3;
|
| - } else {
|
| - uri = uri.substring(start, portStart) +
|
| - uri.substring(pathStart, end);
|
| - schemeEnd -= start;
|
| - hostStart -= start;
|
| - portStart -= start;
|
| - pathStart -= 3 + start;
|
| - queryStart -= 3 + start;
|
| - fragmentStart -= 3 + start;
|
| - start = 0;
|
| - end = uri.length;
|
| - }
|
| - }
|
| - }
|
| - } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
|
| - scheme = "https";
|
| - // HTTPS URIs should not have an explicit port of 443.
|
| - if (portStart > start && portStart + 4 == pathStart &&
|
| - uri.startsWith("443", portStart + 1)) {
|
| - if (start == 0 && end == uri.length) {
|
| - uri = uri.replaceRange(portStart, pathStart, "");
|
| - pathStart -= 4;
|
| - queryStart -= 4;
|
| - fragmentStart -= 4;
|
| - end -= 3;
|
| - } else {
|
| - uri = uri.substring(start, portStart) +
|
| - uri.substring(pathStart, end);
|
| - schemeEnd -= start;
|
| - hostStart -= start;
|
| - portStart -= start;
|
| - pathStart -= 4 + start;
|
| - queryStart -= 4 + start;
|
| - fragmentStart -= 4 + start;
|
| - start = 0;
|
| - end = uri.length;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (isSimple) {
|
| - if (start > 0 || end < uri.length) {
|
| - uri = uri.substring(start, end);
|
| - schemeEnd -= start;
|
| - hostStart -= start;
|
| - portStart -= start;
|
| - pathStart -= start;
|
| - queryStart -= start;
|
| - fragmentStart -= start;
|
| - }
|
| - return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
|
| - queryStart, fragmentStart, scheme);
|
| -
|
| - }
|
| -
|
| - return new _Uri.notSimple(uri, start, end, schemeEnd, hostStart, portStart,
|
| - pathStart, queryStart, fragmentStart, scheme);
|
| - }
|
| -
|
| - /**
|
| - * Encode the string [component] using percent-encoding to make it
|
| - * safe for literal use as a URI component.
|
| - *
|
| - * All characters except uppercase and lowercase letters, digits and
|
| - * the characters `-_.!~*'()` are percent-encoded. This is the
|
| - * set of characters specified in RFC 2396 and the which is
|
| - * specified for the encodeUriComponent in ECMA-262 version 5.1.
|
| - *
|
| - * When manually encoding path segments or query components remember
|
| - * to encode each part separately before building the path or query
|
| - * string.
|
| - *
|
| - * For encoding the query part consider using
|
| - * [encodeQueryComponent].
|
| - *
|
| - * To avoid the need for explicitly encoding use the [pathSegments]
|
| - * and [queryParameters] optional named arguments when constructing
|
| - * a [Uri].
|
| - */
|
| - static String encodeComponent(String component) {
|
| - return _Uri._uriEncode(_Uri._unreserved2396Table, component, UTF8, false);
|
| - }
|
| -
|
| - /**
|
| - * Encode the string [component] according to the HTML 4.01 rules
|
| - * for encoding the posting of a HTML form as a query string
|
| - * component.
|
| - *
|
| - * Encode the string [component] according to the HTML 4.01 rules
|
| - * for encoding the posting of a HTML form as a query string
|
| - * component.
|
| -
|
| - * The component is first encoded to bytes using [encoding].
|
| - * The default is to use [UTF8] encoding, which preserves all
|
| - * the characters that don't need encoding.
|
| -
|
| - * Then the resulting bytes are "percent-encoded". This transforms
|
| - * spaces (U+0020) to a plus sign ('+') and all bytes that are not
|
| - * the ASCII decimal digits, letters or one of '-._~' are written as
|
| - * a percent sign '%' followed by the two-digit hexadecimal
|
| - * representation of the byte.
|
| -
|
| - * Note that the set of characters which are percent-encoded is a
|
| - * superset of what HTML 4.01 requires, since it refers to RFC 1738
|
| - * for reserved characters.
|
| - *
|
| - * When manually encoding query components remember to encode each
|
| - * part separately before building the query string.
|
| - *
|
| - * To avoid the need for explicitly encoding the query use the
|
| - * [queryParameters] optional named arguments when constructing a
|
| - * [Uri].
|
| - *
|
| - * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
|
| - * details.
|
| - */
|
| - static String encodeQueryComponent(String component,
|
| - {Encoding encoding: UTF8}) {
|
| - return _Uri._uriEncode(_Uri._unreservedTable, component, encoding, true);
|
| - }
|
| -
|
| - /**
|
| - * Decodes the percent-encoding in [encodedComponent].
|
| - *
|
| - * Note that decoding a URI component might change its meaning as
|
| - * some of the decoded characters could be characters with are
|
| - * delimiters for a given URI componene type. Always split a URI
|
| - * component using the delimiters for the component before decoding
|
| - * the individual parts.
|
| - *
|
| - * For handling the [path] and [query] components consider using
|
| - * [pathSegments] and [queryParameters] to get the separated and
|
| - * decoded component.
|
| - */
|
| - static String decodeComponent(String encodedComponent) {
|
| - return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
|
| - UTF8, false);
|
| - }
|
| -
|
| - /**
|
| - * Decodes the percent-encoding in [encodedComponent], converting
|
| - * pluses to spaces.
|
| - *
|
| - * It will create a byte-list of the decoded characters, and then use
|
| - * [encoding] to decode the byte-list to a String. The default encoding is
|
| - * UTF-8.
|
| - */
|
| - static String decodeQueryComponent(
|
| - String encodedComponent,
|
| - {Encoding encoding: UTF8}) {
|
| - return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
|
| - encoding, true);
|
| - }
|
| -
|
| - /**
|
| - * Encode the string [uri] using percent-encoding to make it
|
| - * safe for literal use as a full URI.
|
| - *
|
| - * All characters except uppercase and lowercase letters, digits and
|
| - * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
|
| - * is the set of characters specified in in ECMA-262 version 5.1 for
|
| - * the encodeURI function .
|
| - */
|
| - static String encodeFull(String uri) {
|
| - return _Uri._uriEncode(_Uri._encodeFullTable, uri, UTF8, false);
|
| - }
|
| -
|
| - /**
|
| - * Decodes the percent-encoding in [uri].
|
| - *
|
| - * Note that decoding a full URI might change its meaning as some of
|
| - * the decoded characters could be reserved characters. In most
|
| - * cases an encoded URI should be parsed into components using
|
| - * [Uri.parse] before decoding the separate components.
|
| - */
|
| - static String decodeFull(String uri) {
|
| - return _Uri._uriDecode(uri, 0, uri.length, UTF8, false);
|
| - }
|
| -
|
| - /**
|
| - * Returns the [query] split into a map according to the rules
|
| - * specified for FORM post in the [HTML 4.01 specification section
|
| - * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| - * Each key and value in the returned map has been decoded. If the [query]
|
| - * is the empty string an empty map is returned.
|
| - *
|
| - * Keys in the query string that have no value are mapped to the
|
| - * empty string.
|
| - *
|
| - * Each query component will be decoded using [encoding]. The default encoding
|
| - * is UTF-8.
|
| - */
|
| - static Map<String, String> splitQueryString(String query,
|
| - {Encoding encoding: UTF8}) {
|
| - return query.split("&").fold({}, (map, element) {
|
| - int index = element.indexOf("=");
|
| - if (index == -1) {
|
| - if (element != "") {
|
| - map[decodeQueryComponent(element, encoding: encoding)] = "";
|
| - }
|
| - } else if (index != 0) {
|
| - var key = element.substring(0, index);
|
| - var value = element.substring(index + 1);
|
| - map[decodeQueryComponent(key, encoding: encoding)] =
|
| - decodeQueryComponent(value, encoding: encoding);
|
| - }
|
| - return map;
|
| - });
|
| - }
|
| -
|
| -
|
| - /**
|
| - * Parse the [host] as an IP version 4 (IPv4) address, returning the address
|
| - * as a list of 4 bytes in network byte order (big endian).
|
| - *
|
| - * Throws a [FormatException] if [host] is not a valid IPv4 address
|
| - * representation.
|
| - */
|
| - static List<int> parseIPv4Address(String host) =>
|
| - _parseIPv4Address(host, 0, host.length);
|
| -
|
| - /// Implementation of [parseIPv4Address] that can work on a substring.
|
| - static List<int> _parseIPv4Address(String host, int start, int end) {
|
| - void error(String msg, int position) {
|
| - throw new FormatException('Illegal IPv4 address, $msg', host, position);
|
| - }
|
| -
|
| - var result = new Uint8List(4);
|
| - int partIndex = 0;
|
| - int partStart = start;
|
| - for (int i = start; i < end; i++) {
|
| - int char = host.codeUnitAt(i);
|
| - if (char != _DOT) {
|
| - if (char ^ 0x30 > 9) {
|
| - // Fail on a non-digit character.
|
| - error("invalid character", i);
|
| - }
|
| - } else {
|
| - if (partIndex == 3) {
|
| - error('IPv4 address should contain exactly 4 parts', i);
|
| - }
|
| - int part = int.parse(host.substring(partStart, i));
|
| - if (part > 255) {
|
| - error("each part must be in the range 0..255", partStart);
|
| - }
|
| - result[partIndex++] = part;
|
| - partStart = i + 1;
|
| - }
|
| - }
|
| -
|
| - if (partIndex != 3) {
|
| - error('IPv4 address should contain exactly 4 parts', end);
|
| - }
|
| -
|
| - int part = int.parse(host.substring(partStart, end));
|
| - if (part > 255) {
|
| - error("each part must be in the range 0..255", partStart);
|
| - }
|
| - result[partIndex] = part;
|
| -
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Parse the [host] as an IP version 6 (IPv6) address, returning the address
|
| - * as a list of 16 bytes in network byte order (big endian).
|
| - *
|
| - * Throws a [FormatException] if [host] is not a valid IPv6 address
|
| - * representation.
|
| - *
|
| - * Acts on the substring from [start] to [end]. If [end] is omitted, it
|
| - * defaults ot the end of the string.
|
| - *
|
| - * Some examples of IPv6 addresses:
|
| - * * ::1
|
| - * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
|
| - * * 3ffe:2a00:100:7031::1
|
| - * * ::FFFF:129.144.52.38
|
| - * * 2010:836B:4179::836B:4179
|
| - */
|
| - static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
|
| - if (end == null) end = host.length;
|
| - // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, separated
|
| - // by `:`'s, with the following exceptions:
|
| - //
|
| - // - One (and only one) wildcard (`::`) may be present, representing a fill
|
| - // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
|
| - // - The last two parts may be replaced by an IPv4 "dotted-quad" address.
|
| + // fragment = *( pchar / "/" / "?" )
|
| + const int EOI = -1;
|
|
|
| - // Helper function for reporting a badly formatted IPv6 address.
|
| - void error(String msg, [position]) {
|
| - throw new FormatException('Illegal IPv6 address, $msg', host, position);
|
| - }
|
| + String scheme = "";
|
| + String userinfo = "";
|
| + String host = null;
|
| + int port = null;
|
| + String path = null;
|
| + String query = null;
|
| + String fragment = null;
|
| + if (end == null) end = uri.length;
|
|
|
| - // Parse a hex block.
|
| - int parseHex(int start, int end) {
|
| - if (end - start > 4) {
|
| - error('an IPv6 part can only contain a maximum of 4 hex digits', start);
|
| + int index = start;
|
| + int pathStart = start;
|
| + // End of input-marker.
|
| + int char = EOI;
|
| +
|
| + void parseAuth() {
|
| + if (index == end) {
|
| + char = EOI;
|
| + return;
|
| + }
|
| + int authStart = index;
|
| + int lastColon = -1;
|
| + int lastAt = -1;
|
| + char = uri.codeUnitAt(index);
|
| + while (index < end) {
|
| + char = uri.codeUnitAt(index);
|
| + if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
|
| + break;
|
| + }
|
| + if (char == _AT_SIGN) {
|
| + lastAt = index;
|
| + lastColon = -1;
|
| + } else if (char == _COLON) {
|
| + lastColon = index;
|
| + } else if (char == _LEFT_BRACKET) {
|
| + lastColon = -1;
|
| + int endBracket = uri.indexOf(']', index + 1);
|
| + if (endBracket == -1) {
|
| + index = end;
|
| + char = EOI;
|
| + break;
|
| + } else {
|
| + index = endBracket;
|
| + }
|
| + }
|
| + index++;
|
| + char = EOI;
|
| + }
|
| + int hostStart = authStart;
|
| + int hostEnd = index;
|
| + if (lastAt >= 0) {
|
| + userinfo = _makeUserInfo(uri, authStart, lastAt);
|
| + hostStart = lastAt + 1;
|
| + }
|
| + if (lastColon >= 0) {
|
| + int portNumber;
|
| + if (lastColon + 1 < index) {
|
| + portNumber = 0;
|
| + for (int i = lastColon + 1; i < index; i++) {
|
| + int digit = uri.codeUnitAt(i);
|
| + if (_ZERO > digit || _NINE < digit) {
|
| + _fail(uri, i, "Invalid port number");
|
| + }
|
| + portNumber = portNumber * 10 + (digit - _ZERO);
|
| + }
|
| + }
|
| + port = _makePort(portNumber, scheme);
|
| + hostEnd = lastColon;
|
| + }
|
| + host = _makeHost(uri, hostStart, hostEnd, true);
|
| + if (index < end) {
|
| + char = uri.codeUnitAt(index);
|
| + }
|
| + }
|
| +
|
| + // When reaching path parsing, the current character is known to not
|
| + // be part of the path.
|
| + const int NOT_IN_PATH = 0;
|
| + // When reaching path parsing, the current character is part
|
| + // of the a non-empty path.
|
| + const int IN_PATH = 1;
|
| + // When reaching authority parsing, authority is possible.
|
| + // This is only true at start or right after scheme.
|
| + const int ALLOW_AUTH = 2;
|
| +
|
| + // Current state.
|
| + // Initialized to the default value that is used when exiting the
|
| + // scheme loop by reaching the end of input.
|
| + // All other breaks set their own state.
|
| + int state = NOT_IN_PATH;
|
| + int i = index; // Temporary alias for index to avoid bug 19550 in dart2js.
|
| + while (i < end) {
|
| + char = uri.codeUnitAt(i);
|
| + if (char == _QUESTION || char == _NUMBER_SIGN) {
|
| + state = NOT_IN_PATH;
|
| + break;
|
| }
|
| - int value = int.parse(host.substring(start, end), radix: 16);
|
| - if (value < 0 || value > 0xFFFF) {
|
| - error('each part must be in the range of `0x0..0xFFFF`', start);
|
| + if (char == _SLASH) {
|
| + state = (i == start) ? ALLOW_AUTH : IN_PATH;
|
| + break;
|
| }
|
| - return value;
|
| - }
|
| -
|
| - if (host.length < 2) error('address is too short');
|
| - List<int> parts = [];
|
| - bool wildcardSeen = false;
|
| - // Set if seeing a ".", suggesting that there is an IPv4 address.
|
| - bool seenDot = false;
|
| - int partStart = start;
|
| - // Parse all parts, except a potential last one.
|
| - for (int i = start; i < end; i++) {
|
| - int char = host.codeUnitAt(i);
|
| if (char == _COLON) {
|
| - if (i == start) {
|
| - // If we see a `:` in the beginning, expect wildcard.
|
| - i++;
|
| - if (host.codeUnitAt(i) != _COLON) {
|
| - error('invalid start colon.', i);
|
| - }
|
| - partStart = i;
|
| + if (i == start) _fail(uri, start, "Invalid empty scheme");
|
| + scheme = _makeScheme(uri, start, i);
|
| + i++;
|
| + if (scheme == "data") {
|
| + // This generates a URI that is (potentially) not path normalized.
|
| + // Applying part normalization to a non-hierarchial URI isn't
|
| + // meaningful.
|
| + return UriData._parse(uri, i, null).uri;
|
| }
|
| - if (i == partStart) {
|
| - // Wildcard. We only allow one.
|
| - if (wildcardSeen) {
|
| - error('only one wildcard `::` is allowed', i);
|
| - }
|
| - wildcardSeen = true;
|
| - parts.add(-1);
|
| + pathStart = i;
|
| + if (i == end) {
|
| + char = EOI;
|
| + state = NOT_IN_PATH;
|
| } else {
|
| - // Found a single colon. Parse [partStart..i] as a hex entry.
|
| - parts.add(parseHex(partStart, i));
|
| + char = uri.codeUnitAt(i);
|
| + if (char == _QUESTION || char == _NUMBER_SIGN) {
|
| + state = NOT_IN_PATH;
|
| + } else if (char == _SLASH) {
|
| + state = ALLOW_AUTH;
|
| + } else {
|
| + state = IN_PATH;
|
| + }
|
| }
|
| - partStart = i + 1;
|
| - } else if (char == _DOT) {
|
| - seenDot = true;
|
| + break;
|
| }
|
| - }
|
| - if (parts.length == 0) error('too few parts');
|
| - bool atEnd = (partStart == end);
|
| - bool isLastWildcard = (parts.last == -1);
|
| - if (atEnd && !isLastWildcard) {
|
| - error('expected a part after last `:`', end);
|
| - }
|
| - if (!atEnd) {
|
| - if (!seenDot) {
|
| - parts.add(parseHex(partStart, end));
|
| + i++;
|
| + char = EOI;
|
| + }
|
| + index = i; // Remove alias when bug is fixed.
|
| +
|
| + if (state == ALLOW_AUTH) {
|
| + assert(char == _SLASH);
|
| + // Have seen one slash either at start or right after scheme.
|
| + // If two slashes, it's an authority, otherwise it's just the path.
|
| + index++;
|
| + if (index == end) {
|
| + char = EOI;
|
| + state = NOT_IN_PATH;
|
| } else {
|
| - List<int> last = _parseIPv4Address(host, partStart, end);
|
| - parts.add(last[0] << 8 | last[1]);
|
| - parts.add(last[2] << 8 | last[3]);
|
| - }
|
| - }
|
| - if (wildcardSeen) {
|
| - if (parts.length > 7) {
|
| - error('an address with a wildcard must have less than 7 parts');
|
| - }
|
| - } else if (parts.length != 8) {
|
| - error('an address without a wildcard must contain exactly 8 parts');
|
| - }
|
| - List<int> bytes = new Uint8List(16);
|
| - for (int i = 0, index = 0; i < parts.length; i++) {
|
| - int value = parts[i];
|
| - if (value == -1) {
|
| - int wildCardLength = 9 - parts.length;
|
| - for (int j = 0; j < wildCardLength; j++) {
|
| - bytes[index] = 0;
|
| - bytes[index + 1] = 0;
|
| - index += 2;
|
| + char = uri.codeUnitAt(index);
|
| + if (char == _SLASH) {
|
| + index++;
|
| + parseAuth();
|
| + pathStart = index;
|
| + }
|
| + if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
|
| + state = NOT_IN_PATH;
|
| + } else {
|
| + state = IN_PATH;
|
| }
|
| - } else {
|
| - bytes[index] = value >> 8;
|
| - bytes[index + 1] = value & 0xff;
|
| - index += 2;
|
| }
|
| }
|
| - return bytes;
|
| - }
|
| -}
|
| -
|
| -class _Uri implements Uri {
|
| - // We represent the missing scheme as an empty string.
|
| - // A valid scheme cannot be empty.
|
| - final String scheme;
|
| -
|
| - /**
|
| - * The user-info part of the authority.
|
| - *
|
| - * Does not distinguish between an empty user-info and an absent one.
|
| - * The value is always non-null.
|
| - * Is considered absent if [_host] is `null`.
|
| - */
|
| - final String _userInfo;
|
| -
|
| - /**
|
| - * The host name of the URI.
|
| - *
|
| - * Set to `null` if there is no authority in the URI.
|
| - * The host name is the only mandatory part of an authority, so we use
|
| - * it to mark whether an authority part was present or not.
|
| - */
|
| - final String _host;
|
| -
|
| - /**
|
| - * The port number part of the authority.
|
| - *
|
| - * The port. Set to null if there is no port. Normalized to null if
|
| - * the port is the default port for the scheme.
|
| - */
|
| - int _port;
|
| -
|
| - /**
|
| - * The path of the URI.
|
| - *
|
| - * Always non-null.
|
| - */
|
| - String _path;
|
|
|
| - // The query content, or null if there is no query.
|
| - final String _query;
|
| -
|
| - // The fragment content, or null if there is no fragment.
|
| - final String _fragment;
|
| -
|
| - /**
|
| - * Cache the computed return value of [pathSegements].
|
| - */
|
| - List<String> _pathSegments;
|
| -
|
| - /**
|
| - * Cache of the full normalized text representation of the URI.
|
| - */
|
| - String _text;
|
| -
|
| - /**
|
| - * Cache of the hashCode of [_text].
|
| - *
|
| - * Is null until computed.
|
| - */
|
| - int _hashCodeCache;
|
| -
|
| - /**
|
| - * Cache the computed return value of [queryParameters].
|
| - */
|
| - Map<String, String> _queryParameters;
|
| - Map<String, List<String>> _queryParameterLists;
|
| -
|
| - /// Internal non-verifying constructor. Only call with validated arguments.
|
| - _Uri._internal(this.scheme,
|
| - this._userInfo,
|
| - this._host,
|
| - this._port,
|
| - this._path,
|
| - this._query,
|
| - this._fragment);
|
| -
|
| - /// Create a [_Uri] from parts of [uri].
|
| - ///
|
| - /// The parameters specify the start/end of particular components of the URI.
|
| - /// The [scheme] may contain a string representing a normalized scheme
|
| - /// component if one has already been discovered.
|
| - factory _Uri.notSimple(String uri, int start, int end, int schemeEnd,
|
| - int hostStart, int portStart, int pathStart,
|
| - int queryStart, int fragmentStart, String scheme) {
|
| - if (scheme == null) {
|
| - scheme = "";
|
| - if (schemeEnd > start) {
|
| - scheme = _makeScheme(uri, start, schemeEnd);
|
| - } else if (schemeEnd == start) {
|
| - _fail(uri, start, "Invalid empty scheme");
|
| - }
|
| - }
|
| - String userInfo = "";
|
| - String host;
|
| - int port;
|
| - if (hostStart > start) {
|
| - int userInfoStart = schemeEnd + 3;
|
| - if (userInfoStart < hostStart) {
|
| - userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1);
|
| - }
|
| - host = _makeHost(uri, hostStart, portStart, false);
|
| - if (portStart + 1 < pathStart) {
|
| - // Should throw because invalid.
|
| - port = int.parse(uri.substring(portStart + 1, pathStart), onError: (_) {
|
| - throw new FormatException("Invalid port", uri, portStart + 1);
|
| - });
|
| - port = _makePort(port, scheme);
|
| + assert(state == IN_PATH || state == NOT_IN_PATH);
|
| + if (state == IN_PATH) {
|
| + // Characters from pathStart to index (inclusive) are known
|
| + // to be part of the path.
|
| + while (++index < end) {
|
| + char = uri.codeUnitAt(index);
|
| + if (char == _QUESTION || char == _NUMBER_SIGN) {
|
| + break;
|
| + }
|
| + char = EOI;
|
| }
|
| + state = NOT_IN_PATH;
|
| }
|
| - String path = _makePath(uri, pathStart, queryStart, null,
|
| - scheme, host != null);
|
| - String query;
|
| - if (queryStart < fragmentStart) {
|
| - query = _makeQuery(uri, queryStart + 1, fragmentStart, null);
|
| - }
|
| - String fragment;
|
| - if (fragmentStart < end) {
|
| - fragment = _makeFragment(uri, fragmentStart + 1, end);
|
| - }
|
| - return new _Uri._internal(scheme,
|
| - userInfo,
|
| - host,
|
| - port,
|
| - path,
|
| - query,
|
| - fragment);
|
| - }
|
|
|
| - /// Implementation of [Uri.Uri].
|
| - factory _Uri({String scheme,
|
| - String userInfo,
|
| - String host,
|
| - int port,
|
| - String path,
|
| - Iterable<String> pathSegments,
|
| - String query,
|
| - Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
|
| - String fragment}) {
|
| - scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
|
| - userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
|
| - host = _makeHost(host, 0, _stringOrNullLength(host), false);
|
| - // Special case this constructor for backwards compatibility.
|
| - if (query == "") query = null;
|
| - query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
|
| - fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
|
| - port = _makePort(port, scheme);
|
| - bool isFile = (scheme == "file");
|
| - if (host == null &&
|
| - (userInfo.isNotEmpty || port != null || isFile)) {
|
| - host = "";
|
| - }
|
| + assert(state == NOT_IN_PATH);
|
| bool hasAuthority = (host != null);
|
| - path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
|
| - scheme, hasAuthority);
|
| - if (scheme.isEmpty && host == null && !path.startsWith('/')) {
|
| - path = _normalizeRelativePath(path);
|
| - } else {
|
| - path = _removeDotSegments(path);
|
| - }
|
| - return new _Uri._internal(scheme, userInfo, host, port,
|
| - path, query, fragment);
|
| - }
|
| -
|
| - /// Implementation of [Uri.http].
|
| - factory _Uri.http(String authority,
|
| - String unencodedPath,
|
| - [Map<String, String> queryParameters]) {
|
| - return _makeHttpUri("http", authority, unencodedPath, queryParameters);
|
| - }
|
| -
|
| - /// Implementation of [Uri.https].
|
| - factory _Uri.https(String authority,
|
| - String unencodedPath,
|
| - [Map<String, String> queryParameters]) {
|
| - return _makeHttpUri("https", authority, unencodedPath, queryParameters);
|
| - }
|
| + path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
|
|
|
| - String get authority {
|
| - if (!hasAuthority) return "";
|
| - var sb = new StringBuffer();
|
| - _writeAuthority(sb);
|
| - return sb.toString();
|
| - }
|
| -
|
| - String get userInfo => _userInfo;
|
| -
|
| - String get host {
|
| - if (_host == null) return "";
|
| - if (_host.startsWith('[')) {
|
| - return _host.substring(1, _host.length - 1);
|
| + if (char == _QUESTION) {
|
| + int numberSignIndex = -1;
|
| + for (int i = index + 1; i < end; i++) {
|
| + if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
|
| + numberSignIndex = i;
|
| + break;
|
| + }
|
| + }
|
| + if (numberSignIndex < 0) {
|
| + query = _makeQuery(uri, index + 1, end, null);
|
| + } else {
|
| + query = _makeQuery(uri, index + 1, numberSignIndex, null);
|
| + fragment = _makeFragment(uri, numberSignIndex + 1, end);
|
| + }
|
| + } else if (char == _NUMBER_SIGN) {
|
| + fragment = _makeFragment(uri, index + 1, end);
|
| }
|
| - return _host;
|
| - }
|
| -
|
| - int get port {
|
| - if (_port == null) return _defaultPort(scheme);
|
| - return _port;
|
| + return new Uri._internal(scheme,
|
| + userinfo,
|
| + host,
|
| + port,
|
| + path,
|
| + query,
|
| + fragment);
|
| }
|
|
|
| - // The default port for the scheme of this Uri.
|
| - static int _defaultPort(String scheme) {
|
| - if (scheme == "http") return 80;
|
| - if (scheme == "https") return 443;
|
| - return 0;
|
| - }
|
| -
|
| - String get path => _path;
|
| -
|
| - String get query => _query ?? "";
|
| -
|
| - String get fragment => _fragment ?? "";
|
| -
|
| // Report a parse failure.
|
| static void _fail(String uri, int index, String message) {
|
| throw new FormatException(message, uri, index);
|
| @@ -1545,8 +612,7 @@ class _Uri implements Uri {
|
| // Split off the user info.
|
| bool hasUserInfo = false;
|
| for (int i = 0; i < authority.length; i++) {
|
| - const int atSign = 0x40;
|
| - if (authority.codeUnitAt(i) == atSign) {
|
| + if (authority.codeUnitAt(i) == _AT_SIGN) {
|
| hasUserInfo = true;
|
| userInfo = authority.substring(0, i);
|
| hostStart = i + 1;
|
| @@ -1564,7 +630,7 @@ class _Uri implements Uri {
|
| throw new FormatException("Invalid IPv6 host entry.",
|
| authority, hostStart);
|
| }
|
| - Uri.parseIPv6Address(authority, hostStart + 1, hostEnd);
|
| + parseIPv6Address(authority, hostStart + 1, hostEnd);
|
| hostEnd++; // Skip the closing bracket.
|
| if (hostEnd != authority.length &&
|
| authority.codeUnitAt(hostEnd) != _COLON) {
|
| @@ -1592,22 +658,185 @@ class _Uri implements Uri {
|
| queryParameters: queryParameters);
|
| }
|
|
|
| - /// Implementation of [Uri.file].
|
| - factory _Uri.file(String path, {bool windows}) {
|
| - windows = (windows == null) ? _Uri._isWindows : windows;
|
| + /**
|
| + * Creates a new file URI from an absolute or relative file path.
|
| + *
|
| + * The file path is passed in [path].
|
| + *
|
| + * This path is interpreted using either Windows or non-Windows
|
| + * semantics.
|
| + *
|
| + * With non-Windows semantics the slash ("/") is used to separate
|
| + * path segments.
|
| + *
|
| + * With Windows semantics, backslash ("\") and forward-slash ("/")
|
| + * are used to separate path segments, except if the path starts
|
| + * with "\\?\" in which case, only backslash ("\") separates path
|
| + * segments.
|
| + *
|
| + * If the path starts with a path separator an absolute URI is
|
| + * created. Otherwise a relative URI is created. One exception from
|
| + * this rule is that when Windows semantics is used and the path
|
| + * starts with a drive letter followed by a colon (":") and a
|
| + * path separator then an absolute URI is created.
|
| + *
|
| + * The default for whether to use Windows or non-Windows semantics
|
| + * determined from the platform Dart is running on. When running in
|
| + * the standalone VM this is detected by the VM based on the
|
| + * operating system. When running in a browser non-Windows semantics
|
| + * is always used.
|
| + *
|
| + * To override the automatic detection of which semantics to use pass
|
| + * a value for [windows]. Passing `true` will use Windows
|
| + * semantics and passing `false` will use non-Windows semantics.
|
| + *
|
| + * Examples using non-Windows semantics:
|
| + *
|
| + * ```
|
| + * // xxx/yyy
|
| + * new Uri.file("xxx/yyy", windows: false);
|
| + *
|
| + * // xxx/yyy/
|
| + * new Uri.file("xxx/yyy/", windows: false);
|
| + *
|
| + * // file:///xxx/yyy
|
| + * new Uri.file("/xxx/yyy", windows: false);
|
| + *
|
| + * // file:///xxx/yyy/
|
| + * new Uri.file("/xxx/yyy/", windows: false);
|
| + *
|
| + * // C:
|
| + * new Uri.file("C:", windows: false);
|
| + * ```
|
| + *
|
| + * Examples using Windows semantics:
|
| + *
|
| + * ```
|
| + * // xxx/yyy
|
| + * new Uri.file(r"xxx\yyy", windows: true);
|
| + *
|
| + * // xxx/yyy/
|
| + * new Uri.file(r"xxx\yyy\", windows: true);
|
| + *
|
| + * file:///xxx/yyy
|
| + * new Uri.file(r"\xxx\yyy", windows: true);
|
| + *
|
| + * file:///xxx/yyy/
|
| + * new Uri.file(r"\xxx\yyy/", windows: true);
|
| + *
|
| + * // file:///C:/xxx/yyy
|
| + * new Uri.file(r"C:\xxx\yyy", windows: true);
|
| + *
|
| + * // This throws an error. A path with a drive letter is not absolute.
|
| + * new Uri.file(r"C:", windows: true);
|
| + *
|
| + * // This throws an error. A path with a drive letter is not absolute.
|
| + * new Uri.file(r"C:xxx\yyy", windows: true);
|
| + *
|
| + * // file://server/share/file
|
| + * new Uri.file(r"\\server\share\file", windows: true);
|
| + * ```
|
| + *
|
| + * If the path passed is not a legal file path [ArgumentError] is thrown.
|
| + */
|
| + factory Uri.file(String path, {bool windows}) {
|
| + windows = (windows == null) ? Uri._isWindows : windows;
|
| return windows ? _makeWindowsFileUrl(path, false)
|
| : _makeFileUri(path, false);
|
| }
|
|
|
| - /// Implementation of [Uri.directory].
|
| - factory _Uri.directory(String path, {bool windows}) {
|
| - windows = (windows == null) ? _Uri._isWindows : windows;
|
| + /**
|
| + * Like [Uri.file] except that a non-empty URI path ends in a slash.
|
| + *
|
| + * If [path] is not empty, and it doesn't end in a directory separator,
|
| + * then a slash is added to the returned URI's path.
|
| + * In all other cases, the result is the same as returned by `Uri.file`.
|
| + */
|
| + factory Uri.directory(String path, {bool windows}) {
|
| + windows = (windows == null) ? Uri._isWindows : windows;
|
| return windows ? _makeWindowsFileUrl(path, true)
|
| : _makeFileUri(path, true);
|
| }
|
|
|
| + /**
|
| + * Creates a `data:` URI containing the [content] string.
|
| + *
|
| + * Converts the content to a bytes using [encoding] or the charset specified
|
| + * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
|
| + * then encodes the bytes into the resulting data URI.
|
| + *
|
| + * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
|
| + * bytes is replaced by a percent encoding). If [base64] is true, the bytes
|
| + * are instead encoded using [BASE64].
|
| + *
|
| + * If [encoding] is not provided and [parameters] has a `charset` entry,
|
| + * that name is looked up using [Encoding.getByName],
|
| + * and if the lookup returns an encoding, that encoding is used to convert
|
| + * [content] to bytes.
|
| + * If providing both an [encoding] and a charset [parameter], they should
|
| + * agree, otherwise decoding won't be able to use the charset parameter
|
| + * to determine the encoding.
|
| + *
|
| + * If [mimeType] and/or [parameters] are supplied, they are added to the
|
| + * created URI. If any of these contain characters that are not allowed
|
| + * in the data URI, the character is percent-escaped. If the character is
|
| + * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
|
| + * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
|
| + * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
|
| + *
|
| + * To read the content back, use [UriData.contentAsString].
|
| + */
|
| + factory Uri.dataFromString(String content,
|
| + {String mimeType,
|
| + Encoding encoding,
|
| + Map<String, String> parameters,
|
| + bool base64: false}) {
|
| + UriData data = new UriData.fromString(content,
|
| + mimeType: mimeType,
|
| + encoding: encoding,
|
| + parameters: parameters,
|
| + base64: base64);
|
| + return data.uri;
|
| + }
|
| +
|
| + /**
|
| + * Creates a `data:` URI containing an encoding of [bytes].
|
| + *
|
| + * Defaults to Base64 encoding the bytes, but if [percentEncoded]
|
| + * is `true`, the bytes will instead be percent encoded (any non-ASCII
|
| + * or non-valid-ASCII-character byte is replaced by a percent encoding).
|
| + *
|
| + * To read the bytes back, use [UriData.contentAsBytes].
|
| + *
|
| + * It defaults to having the mime-type `application/octet-stream`.
|
| + * The [mimeType] and [parameters] are added to the created URI.
|
| + * If any of these contain characters that are not allowed
|
| + * in the data URI, the character is percent-escaped. If the character is
|
| + * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
|
| + * encoded.
|
| + */
|
| + factory Uri.dataFromBytes(List<int> bytes,
|
| + {mimeType: "application/octet-stream",
|
| + Map<String, String> parameters,
|
| + percentEncoded: false}) {
|
| + UriData data = new UriData.fromBytes(bytes,
|
| + mimeType: mimeType,
|
| + parameters: parameters,
|
| + percentEncoded: percentEncoded);
|
| + return data.uri;
|
| + }
|
| +
|
| + /**
|
| + * Returns the natural base URI for the current platform.
|
| + *
|
| + * When running in a browser this is the current URL (from
|
| + * `window.location.href`).
|
| + *
|
| + * When not running in a browser this is the file URI referencing
|
| + * the current working directory.
|
| + */
|
| + external static Uri get base;
|
|
|
| - /// Used internally in path-related constructors.
|
| external static bool get _isWindows;
|
|
|
| static _checkNonWindowsPathReservedCharacters(List<String> segments,
|
| @@ -1740,6 +969,46 @@ class _Uri implements Uri {
|
| }
|
| }
|
|
|
| + /**
|
| + * Returns a new `Uri` based on this one, but with some parts replaced.
|
| + *
|
| + * This method takes the same parameters as the [new Uri] constructor,
|
| + * and they have the same meaning.
|
| + *
|
| + * At most one of [path] and [pathSegments] must be provided.
|
| + * Likewise, at most one of [query] and [queryParameters] must be provided.
|
| + *
|
| + * Each part that is not provided will default to the corresponding
|
| + * value from this `Uri` instead.
|
| + *
|
| + * This method is different from [Uri.resolve] which overrides in a
|
| + * hierarchial manner,
|
| + * and can instead replace each part of a `Uri` individually.
|
| + *
|
| + * Example:
|
| + *
|
| + * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
|
| + * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
|
| + * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
|
| + *
|
| + * This method acts similarly to using the `new Uri` constructor with
|
| + * some of the arguments taken from this `Uri` . Example:
|
| + *
|
| + * Uri uri3 = new Uri(
|
| + * scheme: "A",
|
| + * userInfo: uri1.userInfo,
|
| + * host: uri1.host,
|
| + * port: uri1.port,
|
| + * path: "D/E/E",
|
| + * query: uri1.query,
|
| + * fragment: "G");
|
| + * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
|
| + * print(uri2 == uri3); // prints true.
|
| + *
|
| + * Using this method can be seen as a shorthand for the `Uri` constructor
|
| + * call above, but may also be slightly faster because the parts taken
|
| + * from this `Uri` need not be checked for validity again.
|
| + */
|
| Uri replace({String scheme,
|
| String userInfo,
|
| String host,
|
| @@ -1806,16 +1075,29 @@ class _Uri implements Uri {
|
| fragment = this._fragment;
|
| }
|
|
|
| - return new _Uri._internal(
|
| + return new Uri._internal(
|
| scheme, userInfo, host, port, path, query, fragment);
|
| }
|
|
|
| + /**
|
| + * Returns a `Uri` that differs from this only in not having a fragment.
|
| + *
|
| + * If this `Uri` does not have a fragment, it is itself returned.
|
| + */
|
| Uri removeFragment() {
|
| if (!this.hasFragment) return this;
|
| - return new _Uri._internal(scheme, _userInfo, _host, _port,
|
| + return new Uri._internal(scheme, _userInfo, _host, _port,
|
| _path, _query, null);
|
| }
|
|
|
| + /**
|
| + * Returns the URI path split into its segments. Each of the segments in the
|
| + * returned list have been decoded. If the path is empty the empty list will
|
| + * be returned. A leading slash `/` does not affect the segments returned.
|
| + *
|
| + * The returned list is unmodifiable and will throw [UnsupportedError] on any
|
| + * calls that would mutate it.
|
| + */
|
| List<String> get pathSegments {
|
| var result = _pathSegments;
|
| if (result != null) return result;
|
| @@ -1832,14 +1114,43 @@ class _Uri implements Uri {
|
| return result;
|
| }
|
|
|
| + /**
|
| + * Returns the URI query split into a map according to the rules
|
| + * specified for FORM post in the [HTML 4.01 specification section
|
| + * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| + * Each key and value in the returned map has been decoded.
|
| + * If there is no query the empty map is returned.
|
| + *
|
| + * Keys in the query string that have no value are mapped to the
|
| + * empty string.
|
| + * If a key occurs more than once in the query string, it is mapped to
|
| + * an arbitrary choice of possible value.
|
| + * The [queryParametersAll] getter can provide a map
|
| + * that maps keys to all of their values.
|
| + *
|
| + * The returned map is unmodifiable.
|
| + */
|
| Map<String, String> get queryParameters {
|
| if (_queryParameters == null) {
|
| _queryParameters =
|
| - new UnmodifiableMapView<String, String>(Uri.splitQueryString(query));
|
| + new UnmodifiableMapView<String, String>(splitQueryString(query));
|
| }
|
| return _queryParameters;
|
| }
|
|
|
| + /**
|
| + * Returns the URI query split into a map according to the rules
|
| + * specified for FORM post in the [HTML 4.01 specification section
|
| + * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| + * Each key and value in the returned map has been decoded. If there is no
|
| + * query the empty map is returned.
|
| + *
|
| + * Keys are mapped to lists of their values. If a key occurs only once,
|
| + * its value is a singleton list. If a key occurs with no value, the
|
| + * empty string is used as the value for that occurrence.
|
| + *
|
| + * The returned map and the lists it contains are unmodifiable.
|
| + */
|
| Map<String, List<String>> get queryParametersAll {
|
| if (_queryParameterLists == null) {
|
| Map queryParameterLists = _splitQueryStringAll(query);
|
| @@ -1853,6 +1164,22 @@ class _Uri implements Uri {
|
| return _queryParameterLists;
|
| }
|
|
|
| + /**
|
| + * Returns a URI where the path has been normalized.
|
| + *
|
| + * A normalized path does not contain `.` segments or non-leading `..`
|
| + * segments.
|
| + * Only a relative path with no scheme or authority may contain
|
| + * leading `..` segments,
|
| + * a path that starts with `/` will also drop any leading `..` segments.
|
| + *
|
| + * This uses the same normalization strategy as `new Uri().resolve(this)`.
|
| + *
|
| + * Does not change any part of the URI except the path.
|
| + *
|
| + * The default implementation of `Uri` always normalizes paths, so calling
|
| + * this function has no effect.
|
| + */
|
| Uri normalizePath() {
|
| String path = _normalizePath(_path, scheme, hasAuthority);
|
| if (identical(path, _path)) return this;
|
| @@ -1885,7 +1212,7 @@ class _Uri implements Uri {
|
| if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
|
| _fail(host, start, 'Missing end `]` to match `[` in host');
|
| }
|
| - Uri.parseIPv6Address(host, start + 1, end - 1);
|
| + parseIPv6Address(host, start + 1, end - 1);
|
| // RFC 5952 requires hex digits to be lower case.
|
| return host.substring(start, end).toLowerCase();
|
| }
|
| @@ -1893,7 +1220,7 @@ class _Uri implements Uri {
|
| // TODO(lrn): skip if too short to be a valid IPv6 address?
|
| for (int i = start; i < end; i++) {
|
| if (host.codeUnitAt(i) == _COLON) {
|
| - Uri.parseIPv6Address(host, start, end);
|
| + parseIPv6Address(host, start, end);
|
| return '[$host]';
|
| }
|
| }
|
| @@ -2006,17 +1333,6 @@ class _Uri implements Uri {
|
| }
|
| scheme = scheme.substring(start, end);
|
| if (containsUpperCase) scheme = scheme.toLowerCase();
|
| - return _canonicalizeScheme(scheme);
|
| - }
|
| -
|
| - // Canonicalize a few often-used scheme strings.
|
| - //
|
| - // This improves memory usage and makes comparison faster.
|
| - static String _canonicalizeScheme(String scheme) {
|
| - if (scheme == "http") return "http";
|
| - if (scheme == "file") return "file";
|
| - if (scheme == "https") return "https";
|
| - if (scheme == "package") return "package";
|
| return scheme;
|
| }
|
|
|
| @@ -2103,6 +1419,8 @@ class _Uri implements Uri {
|
| return _normalize(fragment, start, end, _queryCharTable);
|
| }
|
|
|
| + static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
|
| +
|
| /**
|
| * Performs RFC 3986 Percent-Encoding Normalization.
|
| *
|
| @@ -2147,11 +1465,10 @@ class _Uri implements Uri {
|
| // Converts a UTF-16 code-unit to its value as a hex digit.
|
| // Returns -1 for non-hex digits.
|
| static int _parseHexDigit(int char) {
|
| - const int zeroDigit = 0x30;
|
| - int digit = char ^ zeroDigit;
|
| + int digit = char ^ Uri._ZERO;
|
| if (digit <= 9) return digit;
|
| int lowerCase = char | 0x20;
|
| - if (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
|
| + if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
|
| return lowerCase - (_LOWER_CASE_A - 10);
|
| }
|
| return -1;
|
| @@ -2237,7 +1554,7 @@ class _Uri implements Uri {
|
| if (index + 1 < end) {
|
| int tail = component.codeUnitAt(index + 1);
|
| if ((tail & 0xFC00) == 0xDC00) {
|
| - // Tail surrogate.
|
| + // Tail surrogat.
|
| sourceLength = 2;
|
| char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
|
| }
|
| @@ -2386,10 +1703,34 @@ class _Uri implements Uri {
|
| return output.join("/");
|
| }
|
|
|
| + /**
|
| + * Resolve [reference] as an URI relative to `this`.
|
| + *
|
| + * First turn [reference] into a URI using [Uri.parse]. Then resolve the
|
| + * resulting URI relative to `this`.
|
| + *
|
| + * Returns the resolved URI.
|
| + *
|
| + * See [resolveUri] for details.
|
| + */
|
| Uri resolve(String reference) {
|
| return resolveUri(Uri.parse(reference));
|
| }
|
|
|
| + /**
|
| + * Resolve [reference] as an URI relative to `this`.
|
| + *
|
| + * Returns the resolved URI.
|
| + *
|
| + * The algorithm "Transform Reference" for resolving a reference is described
|
| + * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
|
| + *
|
| + * Updated to handle the case where the base URI is just a relative path -
|
| + * that is: when it has no scheme or authority and the path does not start
|
| + * with a slash.
|
| + * In that case, the paths are combined without removing leading "..", and
|
| + * an empty path is not converted to "/".
|
| + */
|
| Uri resolveUri(Uri reference) {
|
| // From RFC 3986.
|
| String targetScheme;
|
| @@ -2435,17 +1776,11 @@ class _Uri implements Uri {
|
| } else {
|
| // This is the RFC 3986 behavior for merging.
|
| if (this.hasEmptyPath) {
|
| - if (!this.hasAuthority) {
|
| - if (!this.hasScheme) {
|
| - // Keep the path relative if no scheme or authority.
|
| - targetPath = reference.path;
|
| - } else {
|
| - // Remove leading dot-segments if the path is put
|
| - // beneath a scheme.
|
| - targetPath = _removeDotSegments(reference.path);
|
| - }
|
| + if (!this.hasScheme && !this.hasAuthority) {
|
| + // Keep the path relative if no scheme or authority.
|
| + targetPath = reference.path;
|
| } else {
|
| - // RFC algorithm for base with authority and empty path.
|
| + // Add path normalization on top of RFC algorithm.
|
| targetPath = _removeDotSegments("/" + reference.path);
|
| }
|
| } else {
|
| @@ -2453,9 +1788,8 @@ class _Uri implements Uri {
|
| if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
|
| targetPath = _removeDotSegments(mergedPath);
|
| } else {
|
| - // Non-RFC 3986 behavior.
|
| - // If both base and reference are relative paths,
|
| - // allow the merged path to start with "..".
|
| + // Non-RFC 3986 beavior. If both base and reference are relative
|
| + // path, allow the merged path to start with "..".
|
| // The RFC only specifies the case where the base has a scheme.
|
| targetPath = _normalizeRelativePath(mergedPath);
|
| }
|
| @@ -2466,208 +1800,626 @@ class _Uri implements Uri {
|
| }
|
| }
|
| String fragment = reference.hasFragment ? reference.fragment : null;
|
| - return new _Uri._internal(targetScheme,
|
| - targetUserInfo,
|
| - targetHost,
|
| - targetPort,
|
| - targetPath,
|
| - targetQuery,
|
| - fragment);
|
| + return new Uri._internal(targetScheme,
|
| + targetUserInfo,
|
| + targetHost,
|
| + targetPort,
|
| + targetPath,
|
| + targetQuery,
|
| + fragment);
|
| + }
|
| +
|
| + /**
|
| + * Returns whether the URI has a [scheme] component.
|
| + */
|
| + bool get hasScheme => scheme.isNotEmpty;
|
| +
|
| + /**
|
| + * Returns whether the URI has an [authority] component.
|
| + */
|
| + bool get hasAuthority => _host != null;
|
| +
|
| + /**
|
| + * Returns whether the URI has an explicit port.
|
| + *
|
| + * If the port number is the default port number
|
| + * (zero for unrecognized schemes, with http (80) and https (443) being
|
| + * recognized),
|
| + * then the port is made implicit and omitted from the URI.
|
| + */
|
| + bool get hasPort => _port != null;
|
| +
|
| + /**
|
| + * Returns whether the URI has a query part.
|
| + */
|
| + bool get hasQuery => _query != null;
|
| +
|
| + /**
|
| + * Returns whether the URI has a fragment part.
|
| + */
|
| + bool get hasFragment => _fragment != null;
|
| +
|
| + /**
|
| + * Returns whether the URI has an empty path.
|
| + */
|
| + bool get hasEmptyPath => _path.isEmpty;
|
| +
|
| + /**
|
| + * Returns whether the URI has an absolute path (starting with '/').
|
| + */
|
| + bool get hasAbsolutePath => _path.startsWith('/');
|
| +
|
| + /**
|
| + * Returns the origin of the URI in the form scheme://host:port for the
|
| + * schemes http and https.
|
| + *
|
| + * It is an error if the scheme is not "http" or "https".
|
| + *
|
| + * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
|
| + */
|
| + String get origin {
|
| + if (scheme == "" || _host == null || _host == "") {
|
| + throw new StateError("Cannot use origin without a scheme: $this");
|
| + }
|
| + if (scheme != "http" && scheme != "https") {
|
| + throw new StateError(
|
| + "Origin is only applicable schemes http and https: $this");
|
| + }
|
| + if (_port == null) return "$scheme://$_host";
|
| + return "$scheme://$_host:$_port";
|
| + }
|
| +
|
| + /**
|
| + * Returns the file path from a file URI.
|
| + *
|
| + * The returned path has either Windows or non-Windows
|
| + * semantics.
|
| + *
|
| + * For non-Windows semantics the slash ("/") is used to separate
|
| + * path segments.
|
| + *
|
| + * For Windows semantics the backslash ("\") separator is used to
|
| + * separate path segments.
|
| + *
|
| + * If the URI is absolute the path starts with a path separator
|
| + * unless Windows semantics is used and the first path segment is a
|
| + * drive letter. When Windows semantics is used a host component in
|
| + * the uri in interpreted as a file server and a UNC path is
|
| + * returned.
|
| + *
|
| + * The default for whether to use Windows or non-Windows semantics
|
| + * determined from the platform Dart is running on. When running in
|
| + * the standalone VM this is detected by the VM based on the
|
| + * operating system. When running in a browser non-Windows semantics
|
| + * is always used.
|
| + *
|
| + * To override the automatic detection of which semantics to use pass
|
| + * a value for [windows]. Passing `true` will use Windows
|
| + * semantics and passing `false` will use non-Windows semantics.
|
| + *
|
| + * If the URI ends with a slash (i.e. the last path component is
|
| + * empty) the returned file path will also end with a slash.
|
| + *
|
| + * With Windows semantics URIs starting with a drive letter cannot
|
| + * be relative to the current drive on the designated drive. That is
|
| + * for the URI `file:///c:abc` calling `toFilePath` will throw as a
|
| + * path segment cannot contain colon on Windows.
|
| + *
|
| + * Examples using non-Windows semantics (resulting of calling
|
| + * toFilePath in comment):
|
| + *
|
| + * Uri.parse("xxx/yyy"); // xxx/yyy
|
| + * Uri.parse("xxx/yyy/"); // xxx/yyy/
|
| + * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
|
| + * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
|
| + * Uri.parse("file:///C:"); // /C:
|
| + * Uri.parse("file:///C:a"); // /C:a
|
| + *
|
| + * Examples using Windows semantics (resulting URI in comment):
|
| + *
|
| + * Uri.parse("xxx/yyy"); // xxx\yyy
|
| + * Uri.parse("xxx/yyy/"); // xxx\yyy\
|
| + * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
|
| + * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
|
| + * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
|
| + * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
|
| + * // cannot contain colon on Windows.
|
| + * Uri.parse("file://server/share/file"); // \\server\share\file
|
| + *
|
| + * If the URI is not a file URI calling this throws
|
| + * [UnsupportedError].
|
| + *
|
| + * If the URI cannot be converted to a file path calling this throws
|
| + * [UnsupportedError].
|
| + */
|
| + String toFilePath({bool windows}) {
|
| + if (scheme != "" && scheme != "file") {
|
| + throw new UnsupportedError(
|
| + "Cannot extract a file path from a $scheme URI");
|
| + }
|
| + if (query != "") {
|
| + throw new UnsupportedError(
|
| + "Cannot extract a file path from a URI with a query component");
|
| + }
|
| + if (fragment != "") {
|
| + throw new UnsupportedError(
|
| + "Cannot extract a file path from a URI with a fragment component");
|
| + }
|
| + if (windows == null) windows = _isWindows;
|
| + return windows ? _toWindowsFilePath() : _toFilePath();
|
| + }
|
| +
|
| + String _toFilePath() {
|
| + if (host != "") {
|
| + throw new UnsupportedError(
|
| + "Cannot extract a non-Windows file path from a file URI "
|
| + "with an authority");
|
| + }
|
| + _checkNonWindowsPathReservedCharacters(pathSegments, false);
|
| + var result = new StringBuffer();
|
| + if (_isPathAbsolute) result.write("/");
|
| + result.writeAll(pathSegments, "/");
|
| + return result.toString();
|
| + }
|
| +
|
| + String _toWindowsFilePath() {
|
| + bool hasDriveLetter = false;
|
| + var segments = pathSegments;
|
| + if (segments.length > 0 &&
|
| + segments[0].length == 2 &&
|
| + segments[0].codeUnitAt(1) == _COLON) {
|
| + _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
|
| + _checkWindowsPathReservedCharacters(segments, false, 1);
|
| + hasDriveLetter = true;
|
| + } else {
|
| + _checkWindowsPathReservedCharacters(segments, false);
|
| + }
|
| + var result = new StringBuffer();
|
| + if (_isPathAbsolute && !hasDriveLetter) result.write("\\");
|
| + if (host != "") {
|
| + result.write("\\");
|
| + result.write(host);
|
| + result.write("\\");
|
| + }
|
| + result.writeAll(segments, "\\");
|
| + if (hasDriveLetter && segments.length == 1) result.write("\\");
|
| + return result.toString();
|
| + }
|
| +
|
| + bool get _isPathAbsolute {
|
| + if (path == null || path.isEmpty) return false;
|
| + return path.startsWith('/');
|
| + }
|
| +
|
| + void _writeAuthority(StringSink ss) {
|
| + if (_userInfo.isNotEmpty) {
|
| + ss.write(_userInfo);
|
| + ss.write("@");
|
| + }
|
| + if (_host != null) ss.write(_host);
|
| + if (_port != null) {
|
| + ss.write(":");
|
| + ss.write(_port);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Access the structure of a `data:` URI.
|
| + *
|
| + * Returns a [UriData] object for `data:` URIs and `null` for all other
|
| + * URIs.
|
| + * The [UriData] object can be used to access the media type and data
|
| + * of a `data:` URI.
|
| + */
|
| + UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + _addIfNonEmpty(sb, scheme, scheme, ':');
|
| + if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
|
| + // File URIS always have the authority, even if it is empty.
|
| + // The empty URI means "localhost".
|
| + sb.write("//");
|
| + _writeAuthority(sb);
|
| + }
|
| + sb.write(path);
|
| + if (_query != null) { sb..write("?")..write(_query); }
|
| + if (_fragment != null) { sb..write("#")..write(_fragment); }
|
| + return sb.toString();
|
| + }
|
| +
|
| + bool operator==(other) {
|
| + if (other is! Uri) return false;
|
| + Uri uri = other;
|
| + return scheme == uri.scheme &&
|
| + hasAuthority == uri.hasAuthority &&
|
| + userInfo == uri.userInfo &&
|
| + host == uri.host &&
|
| + port == uri.port &&
|
| + path == uri.path &&
|
| + hasQuery == uri.hasQuery &&
|
| + query == uri.query &&
|
| + hasFragment == uri.hasFragment &&
|
| + fragment == uri.fragment;
|
| + }
|
| +
|
| + int get hashCode {
|
| + int combine(part, current) {
|
| + // The sum is truncated to 30 bits to make sure it fits into a Smi.
|
| + return (current * 31 + part.hashCode) & 0x3FFFFFFF;
|
| + }
|
| + return combine(scheme, combine(userInfo, combine(host, combine(port,
|
| + combine(path, combine(query, combine(fragment, 1)))))));
|
| + }
|
| +
|
| + static void _addIfNonEmpty(StringBuffer sb, String test,
|
| + String first, String second) {
|
| + if ("" != test) {
|
| + sb.write(first);
|
| + sb.write(second);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Encode the string [component] using percent-encoding to make it
|
| + * safe for literal use as a URI component.
|
| + *
|
| + * All characters except uppercase and lowercase letters, digits and
|
| + * the characters `-_.!~*'()` are percent-encoded. This is the
|
| + * set of characters specified in RFC 2396 and the which is
|
| + * specified for the encodeUriComponent in ECMA-262 version 5.1.
|
| + *
|
| + * When manually encoding path segments or query components remember
|
| + * to encode each part separately before building the path or query
|
| + * string.
|
| + *
|
| + * For encoding the query part consider using
|
| + * [encodeQueryComponent].
|
| + *
|
| + * To avoid the need for explicitly encoding use the [pathSegments]
|
| + * and [queryParameters] optional named arguments when constructing
|
| + * a [Uri].
|
| + */
|
| + static String encodeComponent(String component) {
|
| + return _uriEncode(_unreserved2396Table, component, UTF8, false);
|
| + }
|
| +
|
| + /**
|
| + * Encode the string [component] according to the HTML 4.01 rules
|
| + * for encoding the posting of a HTML form as a query string
|
| + * component.
|
| + *
|
| + * Encode the string [component] according to the HTML 4.01 rules
|
| + * for encoding the posting of a HTML form as a query string
|
| + * component.
|
| +
|
| + * The component is first encoded to bytes using [encoding].
|
| + * The default is to use [UTF8] encoding, which preserves all
|
| + * the characters that don't need encoding.
|
| +
|
| + * Then the resulting bytes are "percent-encoded". This transforms
|
| + * spaces (U+0020) to a plus sign ('+') and all bytes that are not
|
| + * the ASCII decimal digits, letters or one of '-._~' are written as
|
| + * a percent sign '%' followed by the two-digit hexadecimal
|
| + * representation of the byte.
|
| +
|
| + * Note that the set of characters which are percent-encoded is a
|
| + * superset of what HTML 4.01 requires, since it refers to RFC 1738
|
| + * for reserved characters.
|
| + *
|
| + * When manually encoding query components remember to encode each
|
| + * part separately before building the query string.
|
| + *
|
| + * To avoid the need for explicitly encoding the query use the
|
| + * [queryParameters] optional named arguments when constructing a
|
| + * [Uri].
|
| + *
|
| + * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
|
| + * details.
|
| + */
|
| + static String encodeQueryComponent(String component,
|
| + {Encoding encoding: UTF8}) {
|
| + return _uriEncode(_unreservedTable, component, encoding, true);
|
| }
|
|
|
| - bool get hasScheme => scheme.isNotEmpty;
|
| + /**
|
| + * Decodes the percent-encoding in [encodedComponent].
|
| + *
|
| + * Note that decoding a URI component might change its meaning as
|
| + * some of the decoded characters could be characters with are
|
| + * delimiters for a given URI componene type. Always split a URI
|
| + * component using the delimiters for the component before decoding
|
| + * the individual parts.
|
| + *
|
| + * For handling the [path] and [query] components consider using
|
| + * [pathSegments] and [queryParameters] to get the separated and
|
| + * decoded component.
|
| + */
|
| + static String decodeComponent(String encodedComponent) {
|
| + return _uriDecode(encodedComponent, 0, encodedComponent.length,
|
| + UTF8, false);
|
| + }
|
|
|
| - bool get hasAuthority => _host != null;
|
| + /**
|
| + * Decodes the percent-encoding in [encodedComponent], converting
|
| + * pluses to spaces.
|
| + *
|
| + * It will create a byte-list of the decoded characters, and then use
|
| + * [encoding] to decode the byte-list to a String. The default encoding is
|
| + * UTF-8.
|
| + */
|
| + static String decodeQueryComponent(
|
| + String encodedComponent,
|
| + {Encoding encoding: UTF8}) {
|
| + return _uriDecode(encodedComponent, 0, encodedComponent.length,
|
| + encoding, true);
|
| + }
|
|
|
| - bool get hasPort => _port != null;
|
| + /**
|
| + * Encode the string [uri] using percent-encoding to make it
|
| + * safe for literal use as a full URI.
|
| + *
|
| + * All characters except uppercase and lowercase letters, digits and
|
| + * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
|
| + * is the set of characters specified in in ECMA-262 version 5.1 for
|
| + * the encodeURI function .
|
| + */
|
| + static String encodeFull(String uri) {
|
| + return _uriEncode(_encodeFullTable, uri, UTF8, false);
|
| + }
|
|
|
| - bool get hasQuery => _query != null;
|
| + /**
|
| + * Decodes the percent-encoding in [uri].
|
| + *
|
| + * Note that decoding a full URI might change its meaning as some of
|
| + * the decoded characters could be reserved characters. In most
|
| + * cases an encoded URI should be parsed into components using
|
| + * [Uri.parse] before decoding the separate components.
|
| + */
|
| + static String decodeFull(String uri) {
|
| + return _uriDecode(uri, 0, uri.length, UTF8, false);
|
| + }
|
|
|
| - bool get hasFragment => _fragment != null;
|
| + /**
|
| + * Returns the [query] split into a map according to the rules
|
| + * specified for FORM post in the [HTML 4.01 specification section
|
| + * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
|
| + * Each key and value in the returned map has been decoded. If the [query]
|
| + * is the empty string an empty map is returned.
|
| + *
|
| + * Keys in the query string that have no value are mapped to the
|
| + * empty string.
|
| + *
|
| + * Each query component will be decoded using [encoding]. The default encoding
|
| + * is UTF-8.
|
| + */
|
| + static Map<String, String> splitQueryString(String query,
|
| + {Encoding encoding: UTF8}) {
|
| + return query.split("&").fold({}, (map, element) {
|
| + int index = element.indexOf("=");
|
| + if (index == -1) {
|
| + if (element != "") {
|
| + map[decodeQueryComponent(element, encoding: encoding)] = "";
|
| + }
|
| + } else if (index != 0) {
|
| + var key = element.substring(0, index);
|
| + var value = element.substring(index + 1);
|
| + map[Uri.decodeQueryComponent(key, encoding: encoding)] =
|
| + decodeQueryComponent(value, encoding: encoding);
|
| + }
|
| + return map;
|
| + });
|
| + }
|
|
|
| - bool get hasEmptyPath => _path.isEmpty;
|
| + static List _createList() => [];
|
|
|
| - bool get hasAbsolutePath => _path.startsWith('/');
|
| + static Map _splitQueryStringAll(
|
| + String query, {Encoding encoding: UTF8}) {
|
| + Map result = {};
|
| + int i = 0;
|
| + int start = 0;
|
| + int equalsIndex = -1;
|
|
|
| - String get origin {
|
| - if (scheme == "" || _host == null || _host == "") {
|
| - throw new StateError("Cannot use origin without a scheme: $this");
|
| - }
|
| - if (scheme != "http" && scheme != "https") {
|
| - throw new StateError(
|
| - "Origin is only applicable schemes http and https: $this");
|
| + void parsePair(int start, int equalsIndex, int end) {
|
| + String key;
|
| + String value;
|
| + if (start == end) return;
|
| + if (equalsIndex < 0) {
|
| + key = _uriDecode(query, start, end, encoding, true);
|
| + value = "";
|
| + } else {
|
| + key = _uriDecode(query, start, equalsIndex, encoding, true);
|
| + value = _uriDecode(query, equalsIndex + 1, end, encoding, true);
|
| + }
|
| + result.putIfAbsent(key, _createList).add(value);
|
| }
|
| - if (_port == null) return "$scheme://$_host";
|
| - return "$scheme://$_host:$_port";
|
| - }
|
|
|
| - String toFilePath({bool windows}) {
|
| - if (scheme != "" && scheme != "file") {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a $scheme URI");
|
| - }
|
| - if (query != "") {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a URI with a query component");
|
| - }
|
| - if (fragment != "") {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a URI with a fragment component");
|
| + const int _equals = 0x3d;
|
| + const int _ampersand = 0x26;
|
| + while (i < query.length) {
|
| + int char = query.codeUnitAt(i);
|
| + if (char == _equals) {
|
| + if (equalsIndex < 0) equalsIndex = i;
|
| + } else if (char == _ampersand) {
|
| + parsePair(start, equalsIndex, i);
|
| + start = i + 1;
|
| + equalsIndex = -1;
|
| + }
|
| + i++;
|
| }
|
| - if (windows == null) windows = _isWindows;
|
| - return windows ? _toWindowsFilePath(this) : _toFilePath();
|
| + parsePair(start, equalsIndex, i);
|
| + return result;
|
| }
|
|
|
| - String _toFilePath() {
|
| - if (hasAuthority && host != "") {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a non-Windows file path from a file URI "
|
| - "with an authority");
|
| + /**
|
| + * Parse the [host] as an IP version 4 (IPv4) address, returning the address
|
| + * as a list of 4 bytes in network byte order (big endian).
|
| + *
|
| + * Throws a [FormatException] if [host] is not a valid IPv4 address
|
| + * representation.
|
| + */
|
| + static List<int> parseIPv4Address(String host) {
|
| + void error(String msg) {
|
| + throw new FormatException('Illegal IPv4 address, $msg');
|
| }
|
| - // Use path segments to have any escapes unescaped.
|
| - var pathSegments = this.pathSegments;
|
| - _checkNonWindowsPathReservedCharacters(pathSegments, false);
|
| - var result = new StringBuffer();
|
| - if (hasAbsolutePath) result.write("/");
|
| - result.writeAll(pathSegments, "/");
|
| - return result.toString();
|
| + var bytes = host.split('.');
|
| + if (bytes.length != 4) {
|
| + error('IPv4 address should contain exactly 4 parts');
|
| + }
|
| + // TODO(ajohnsen): Consider using Uint8List.
|
| + return bytes
|
| + .map((byteString) {
|
| + int byte = int.parse(byteString);
|
| + if (byte < 0 || byte > 255) {
|
| + error('each part must be in the range of `0..255`');
|
| + }
|
| + return byte;
|
| + })
|
| + .toList();
|
| }
|
|
|
| - static String _toWindowsFilePath(Uri uri) {
|
| - bool hasDriveLetter = false;
|
| - var segments = uri.pathSegments;
|
| - if (segments.length > 0 &&
|
| - segments[0].length == 2 &&
|
| - segments[0].codeUnitAt(1) == _COLON) {
|
| - _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
|
| - _checkWindowsPathReservedCharacters(segments, false, 1);
|
| - hasDriveLetter = true;
|
| - } else {
|
| - _checkWindowsPathReservedCharacters(segments, false, 0);
|
| + /**
|
| + * Parse the [host] as an IP version 6 (IPv6) address, returning the address
|
| + * as a list of 16 bytes in network byte order (big endian).
|
| + *
|
| + * Throws a [FormatException] if [host] is not a valid IPv6 address
|
| + * representation.
|
| + *
|
| + * Acts on the substring from [start] to [end]. If [end] is omitted, it
|
| + * defaults ot the end of the string.
|
| + *
|
| + * Some examples of IPv6 addresses:
|
| + * * ::1
|
| + * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
|
| + * * 3ffe:2a00:100:7031::1
|
| + * * ::FFFF:129.144.52.38
|
| + * * 2010:836B:4179::836B:4179
|
| + */
|
| + static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
|
| + if (end == null) end = host.length;
|
| + // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
|
| + // by `:`'s, with the following exceptions:
|
| + //
|
| + // - One (and only one) wildcard (`::`) may be present, representing a fill
|
| + // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
|
| + // - The last two parts may be replaced by an IPv4 address.
|
| + void error(String msg, [position]) {
|
| + throw new FormatException('Illegal IPv6 address, $msg', host, position);
|
| }
|
| - var result = new StringBuffer();
|
| - if (uri.hasAbsolutePath && !hasDriveLetter) result.write(r"\");
|
| - if (uri.hasAuthority) {
|
| - var host = uri.host;
|
| - if (host.isNotEmpty) {
|
| - result.write(r"\");
|
| - result.write(host);
|
| - result.write(r"\");
|
| + int parseHex(int start, int end) {
|
| + if (end - start > 4) {
|
| + error('an IPv6 part can only contain a maximum of 4 hex digits', start);
|
| }
|
| + int value = int.parse(host.substring(start, end), radix: 16);
|
| + if (value < 0 || value > (1 << 16) - 1) {
|
| + error('each part must be in the range of `0x0..0xFFFF`', start);
|
| + }
|
| + return value;
|
| }
|
| - result.writeAll(segments, r"\");
|
| - if (hasDriveLetter && segments.length == 1) result.write(r"\");
|
| - return result.toString();
|
| - }
|
| -
|
| - bool get _isPathAbsolute {
|
| - return _path != null && _path.startsWith('/');
|
| - }
|
| -
|
| - void _writeAuthority(StringSink ss) {
|
| - if (_userInfo.isNotEmpty) {
|
| - ss.write(_userInfo);
|
| - ss.write("@");
|
| - }
|
| - if (_host != null) ss.write(_host);
|
| - if (_port != null) {
|
| - ss.write(":");
|
| - ss.write(_port);
|
| + if (host.length < 2) error('address is too short');
|
| + List<int> parts = [];
|
| + bool wildcardSeen = false;
|
| + int partStart = start;
|
| + // Parse all parts, except a potential last one.
|
| + for (int i = start; i < end; i++) {
|
| + if (host.codeUnitAt(i) == _COLON) {
|
| + if (i == start) {
|
| + // If we see a `:` in the beginning, expect wildcard.
|
| + i++;
|
| + if (host.codeUnitAt(i) != _COLON) {
|
| + error('invalid start colon.', i);
|
| + }
|
| + partStart = i;
|
| + }
|
| + if (i == partStart) {
|
| + // Wildcard. We only allow one.
|
| + if (wildcardSeen) {
|
| + error('only one wildcard `::` is allowed', i);
|
| + }
|
| + wildcardSeen = true;
|
| + parts.add(-1);
|
| + } else {
|
| + // Found a single colon. Parse [partStart..i] as a hex entry.
|
| + parts.add(parseHex(partStart, i));
|
| + }
|
| + partStart = i + 1;
|
| + }
|
| }
|
| - }
|
| -
|
| - /**
|
| - * Access the structure of a `data:` URI.
|
| - *
|
| - * Returns a [UriData] object for `data:` URIs and `null` for all other
|
| - * URIs.
|
| - * The [UriData] object can be used to access the media type and data
|
| - * of a `data:` URI.
|
| - */
|
| - UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
|
| -
|
| - String toString() {
|
| - return _text ??= _initializeText();
|
| - }
|
| -
|
| - String _initializeText() {
|
| - assert(_text == null);
|
| - StringBuffer sb = new StringBuffer();
|
| - if (scheme.isNotEmpty) sb..write(scheme)..write(":");
|
| - if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
|
| - // File URIS always have the authority, even if it is empty.
|
| - // The empty URI means "localhost".
|
| - sb.write("//");
|
| - _writeAuthority(sb);
|
| + if (parts.length == 0) error('too few parts');
|
| + bool atEnd = (partStart == end);
|
| + bool isLastWildcard = (parts.last == -1);
|
| + if (atEnd && !isLastWildcard) {
|
| + error('expected a part after last `:`', end);
|
| }
|
| - sb.write(path);
|
| - if (_query != null) sb..write("?")..write(_query);
|
| - if (_fragment != null) sb..write("#")..write(_fragment);
|
| - return sb.toString();
|
| - }
|
| -
|
| - bool operator==(other) {
|
| - if (identical(this, other)) return true;
|
| - if (other is Uri) {
|
| - Uri uri = other;
|
| - return scheme == uri.scheme &&
|
| - hasAuthority == uri.hasAuthority &&
|
| - userInfo == uri.userInfo &&
|
| - host == uri.host &&
|
| - port == uri.port &&
|
| - path == uri.path &&
|
| - hasQuery == uri.hasQuery &&
|
| - query == uri.query &&
|
| - hasFragment == uri.hasFragment &&
|
| - fragment == uri.fragment;
|
| + if (!atEnd) {
|
| + try {
|
| + parts.add(parseHex(partStart, end));
|
| + } catch (e) {
|
| + // Failed to parse the last chunk as hex. Try IPv4.
|
| + try {
|
| + List<int> last = parseIPv4Address(host.substring(partStart, end));
|
| + parts.add(last[0] << 8 | last[1]);
|
| + parts.add(last[2] << 8 | last[3]);
|
| + } catch (e) {
|
| + error('invalid end of IPv6 address.', partStart);
|
| + }
|
| + }
|
| }
|
| - return false;
|
| - }
|
| -
|
| - int get hashCode {
|
| - return _hashCodeCache ??= toString().hashCode;
|
| - }
|
| -
|
| - static List _createList() => [];
|
| -
|
| - static Map _splitQueryStringAll(
|
| - String query, {Encoding encoding: UTF8}) {
|
| - Map result = {};
|
| - int i = 0;
|
| - int start = 0;
|
| - int equalsIndex = -1;
|
| -
|
| - void parsePair(int start, int equalsIndex, int end) {
|
| - String key;
|
| - String value;
|
| - if (start == end) return;
|
| - if (equalsIndex < 0) {
|
| - key = _uriDecode(query, start, end, encoding, true);
|
| - value = "";
|
| - } else {
|
| - key = _uriDecode(query, start, equalsIndex, encoding, true);
|
| - value = _uriDecode(query, equalsIndex + 1, end, encoding, true);
|
| + if (wildcardSeen) {
|
| + if (parts.length > 7) {
|
| + error('an address with a wildcard must have less than 7 parts');
|
| }
|
| - result.putIfAbsent(key, _createList).add(value);
|
| + } else if (parts.length != 8) {
|
| + error('an address without a wildcard must contain exactly 8 parts');
|
| }
|
| -
|
| - const int _equals = 0x3d;
|
| - const int _ampersand = 0x26;
|
| - while (i < query.length) {
|
| - int char = query.codeUnitAt(i);
|
| - if (char == _equals) {
|
| - if (equalsIndex < 0) equalsIndex = i;
|
| - } else if (char == _ampersand) {
|
| - parsePair(start, equalsIndex, i);
|
| - start = i + 1;
|
| - equalsIndex = -1;
|
| + List<int> bytes = new Uint8List(16);
|
| + for (int i = 0, index = 0; i < parts.length; i++) {
|
| + int value = parts[i];
|
| + if (value == -1) {
|
| + int wildCardLength = 9 - parts.length;
|
| + for (int j = 0; j < wildCardLength; j++) {
|
| + bytes[index] = 0;
|
| + bytes[index + 1] = 0;
|
| + index += 2;
|
| + }
|
| + } else {
|
| + bytes[index] = value >> 8;
|
| + bytes[index + 1] = value & 0xff;
|
| + index += 2;
|
| }
|
| - i++;
|
| }
|
| - parsePair(start, equalsIndex, i);
|
| - return result;
|
| + return bytes;
|
| }
|
|
|
| + // Frequently used character codes.
|
| + static const int _SPACE = 0x20;
|
| + static const int _DOUBLE_QUOTE = 0x22;
|
| + static const int _NUMBER_SIGN = 0x23;
|
| + static const int _PERCENT = 0x25;
|
| + static const int _ASTERISK = 0x2A;
|
| + static const int _PLUS = 0x2B;
|
| + static const int _DOT = 0x2E;
|
| + static const int _SLASH = 0x2F;
|
| + static const int _ZERO = 0x30;
|
| + static const int _NINE = 0x39;
|
| + static const int _COLON = 0x3A;
|
| + static const int _LESS = 0x3C;
|
| + static const int _GREATER = 0x3E;
|
| + static const int _QUESTION = 0x3F;
|
| + static const int _AT_SIGN = 0x40;
|
| + static const int _UPPER_CASE_A = 0x41;
|
| + static const int _UPPER_CASE_F = 0x46;
|
| + static const int _UPPER_CASE_Z = 0x5A;
|
| + static const int _LEFT_BRACKET = 0x5B;
|
| + static const int _BACKSLASH = 0x5C;
|
| + static const int _RIGHT_BRACKET = 0x5D;
|
| + static const int _LOWER_CASE_A = 0x61;
|
| + static const int _LOWER_CASE_F = 0x66;
|
| + static const int _LOWER_CASE_Z = 0x7A;
|
| + static const int _BAR = 0x7C;
|
| +
|
| + static const String _hexDigits = "0123456789ABCDEF";
|
| +
|
| external static String _uriEncode(List<int> canonicalTable,
|
| String text,
|
| Encoding encoding,
|
| @@ -3189,13 +2941,13 @@ class UriData {
|
| throw new ArgumentError.value(mimeType, "mimeType",
|
| "Invalid MIME type");
|
| }
|
| - buffer.write(_Uri._uriEncode(_tokenCharTable,
|
| - mimeType.substring(0, slashIndex),
|
| - UTF8, false));
|
| + buffer.write(Uri._uriEncode(_tokenCharTable,
|
| + mimeType.substring(0, slashIndex),
|
| + UTF8, false));
|
| buffer.write("/");
|
| - buffer.write(_Uri._uriEncode(_tokenCharTable,
|
| - mimeType.substring(slashIndex + 1),
|
| - UTF8, false));
|
| + buffer.write(Uri._uriEncode(_tokenCharTable,
|
| + mimeType.substring(slashIndex + 1),
|
| + UTF8, false));
|
| }
|
| if (charsetName != null) {
|
| if (indices != null) {
|
| @@ -3203,7 +2955,7 @@ class UriData {
|
| ..add(buffer.length + 8);
|
| }
|
| buffer.write(";charset=");
|
| - buffer.write(_Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
|
| + buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
|
| }
|
| parameters?.forEach((var key, var value) {
|
| if (key.isEmpty) {
|
| @@ -3216,10 +2968,10 @@ class UriData {
|
| if (indices != null) indices.add(buffer.length);
|
| buffer.write(';');
|
| // Encode any non-RFC2045-token character and both '%' and '#'.
|
| - buffer.write(_Uri._uriEncode(_tokenCharTable, key, UTF8, false));
|
| + buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false));
|
| if (indices != null) indices.add(buffer.length);
|
| buffer.write('=');
|
| - buffer.write(_Uri._uriEncode(_tokenCharTable, value, UTF8, false));
|
| + buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
|
| });
|
| }
|
|
|
| @@ -3236,7 +2988,7 @@ class UriData {
|
| int slashIndex = -1;
|
| for (int i = 0; i < mimeType.length; i++) {
|
| var char = mimeType.codeUnitAt(i);
|
| - if (char != _SLASH) continue;
|
| + if (char != Uri._SLASH) continue;
|
| if (slashIndex < 0) {
|
| slashIndex = i;
|
| continue;
|
| @@ -3256,7 +3008,7 @@ class UriData {
|
| * ````
|
| *
|
| * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
|
| - * and `data` is a sequence of URI-characters (RFC-2396 `uric`).
|
| + * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
|
| *
|
| * This means that all the characters must be ASCII, but the URI may contain
|
| * percent-escapes for non-ASCII byte values that need an interpretation
|
| @@ -3267,22 +3019,13 @@ class UriData {
|
| * and `,` delimiters.
|
| *
|
| * Accessing the individual parts may fail later if they turn out to have
|
| - * content that can't be decoded successfully as a string.
|
| + * content that can't be decoded sucessfully as a string.
|
| */
|
| static UriData parse(String uri) {
|
| - if (uri.length >= 5) {
|
| - int dataDelta = _startsWithData(uri, 0);
|
| - if (dataDelta == 0) {
|
| - // Exact match on "data:".
|
| - return _parse(uri, 5, null);
|
| - }
|
| - if (dataDelta == 0x20) {
|
| - // Starts with a non-normalized "data" scheme containing upper-case
|
| - // letters. Parse anyway, but throw away the scheme.
|
| - return _parse(uri.substring(5), 0, null);
|
| - }
|
| + if (!uri.startsWith("data:")) {
|
| + throw new FormatException("Does not start with 'data:'", uri, 0);
|
| }
|
| - throw new FormatException("Does not start with 'data:'", uri, 0);
|
| + return _parse(uri, 5, null);
|
| }
|
|
|
| /**
|
| @@ -3307,7 +3050,7 @@ class UriData {
|
| // That's perfectly reasonable - data URIs are not hierarchical,
|
| // but it may make some consumers stumble.
|
| // Should we at least do escape normalization?
|
| - _uriCache = new _Uri._internal("data", "", null, null, path, query, null);
|
| + _uriCache = new Uri._internal("data", "", null, null, path, query, null);
|
| return _uriCache;
|
| }
|
|
|
| @@ -3332,7 +3075,7 @@ class UriData {
|
| int start = _separatorIndices[0] + 1;
|
| int end = _separatorIndices[1];
|
| if (start == end) return "text/plain";
|
| - return _Uri._uriDecode(_text, start, end, UTF8, false);
|
| + return Uri._uriDecode(_text, start, end, UTF8, false);
|
| }
|
|
|
| /**
|
| @@ -3353,8 +3096,8 @@ class UriData {
|
| var keyStart = _separatorIndices[i] + 1;
|
| var keyEnd = _separatorIndices[i + 1];
|
| if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
|
| - return _Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
|
| - UTF8, false);
|
| + return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
|
| + UTF8, false);
|
| }
|
| }
|
| return "US-ASCII";
|
| @@ -3412,8 +3155,8 @@ class UriData {
|
| result[index++] = codeUnit;
|
| } else {
|
| if (i + 2 < text.length) {
|
| - var digit1 = _Uri._parseHexDigit(text.codeUnitAt(i + 1));
|
| - var digit2 = _Uri._parseHexDigit(text.codeUnitAt(i + 2));
|
| + var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1));
|
| + var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2));
|
| if (digit1 >= 0 && digit2 >= 0) {
|
| int byte = digit1 * 16 + digit2;
|
| result[index++] = byte;
|
| @@ -3434,7 +3177,7 @@ class UriData {
|
| * If the content is Base64 encoded, it will be decoded to bytes and then
|
| * decoded to a string using [encoding].
|
| * If encoding is omitted, the value of a `charset` parameter is used
|
| - * if it is recognized by [Encoding.getByName], otherwise it defaults to
|
| + * if it is recongized by [Encoding.getByName], otherwise it defaults to
|
| * the [ASCII] encoding, which is the default encoding for data URIs
|
| * that do not specify an encoding.
|
| *
|
| @@ -3456,7 +3199,7 @@ class UriData {
|
| var converter = BASE64.decoder.fuse(encoding.decoder);
|
| return converter.convert(text.substring(start));
|
| }
|
| - return _Uri._uriDecode(text, start, text.length, encoding, false);
|
| + return Uri._uriDecode(text, start, text.length, encoding, false);
|
| }
|
|
|
| /**
|
| @@ -3479,8 +3222,8 @@ class UriData {
|
| var start = _separatorIndices[i - 2] + 1;
|
| var equals = _separatorIndices[i - 1];
|
| var end = _separatorIndices[i];
|
| - String key = _Uri._uriDecode(_text, start, equals, UTF8, false);
|
| - String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false);
|
| + String key = Uri._uriDecode(_text, start, equals, UTF8, false);
|
| + String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false);
|
| result[key] = value;
|
| }
|
| return result;
|
| @@ -3563,9 +3306,9 @@ class UriData {
|
| ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
|
| buffer.writeCharCode(byte);
|
| } else {
|
| - buffer.writeCharCode(_PERCENT);
|
| - buffer.writeCharCode(_hexDigits.codeUnitAt(byte >> 4));
|
| - buffer.writeCharCode(_hexDigits.codeUnitAt(byte & 0x0f));
|
| + buffer.writeCharCode(Uri._PERCENT);
|
| + buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
|
| + buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
|
| }
|
| }
|
| if ((byteOr & ~0xFF) != 0) {
|
| @@ -3614,852 +3357,5 @@ class UriData {
|
| // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
|
| //
|
| // This is the same characters as in a URI query (which is URI pchar plus '?')
|
| - static const _uricTable = _Uri._queryCharTable;
|
| -}
|
| -
|
| -// --------------------------------------------------------------------
|
| -// Constants used to read the scanner result.
|
| -// The indices points into the table filled by [_scan] which contains
|
| -// recognized positions in the scanned URI.
|
| -// The `0` index is only used internally.
|
| -
|
| -/// Index of the position of that `:` after a scheme.
|
| -const int _schemeEndIndex = 1;
|
| -/// Index of the position of the character just before the host name.
|
| -const int _hostStartIndex = 2;
|
| -/// Index of the position of the `:` before a port value.
|
| -const int _portStartIndex = 3;
|
| -/// Index of the position of the first character of a path.
|
| -const int _pathStartIndex = 4;
|
| -/// Index of the position of the `?` before a query.
|
| -const int _queryStartIndex = 5;
|
| -/// Index of the position of the `#` before a fragment.
|
| -const int _fragmentStartIndex = 6;
|
| -/// Index of a position where the URI was determined to be "non-simple".
|
| -const int _notSimpleIndex = 7;
|
| -
|
| -// Initial state for scanner.
|
| -const int _uriStart = 00;
|
| -
|
| -// If scanning of a URI terminates in this state or above,
|
| -// consider the URI non-simple
|
| -const int _nonSimpleEndStates = 14;
|
| -
|
| -// Initial state for scheme validation.
|
| -const int _schemeStart = 20;
|
| -
|
| -/// Transition tables used to scan a URI to determine its structure.
|
| -///
|
| -/// The tables represent a state machine with output.
|
| -///
|
| -/// To scan the URI, start in the [_uriStart] state, then read each character
|
| -/// of the URI in order, from start to end, and for each character perform a
|
| -/// transition to a new state while writing the current position into the output
|
| -/// buffer at a designated index.
|
| -///
|
| -/// Each state, represented by an integer which is an index into
|
| -/// [_scannerTables], has a set of transitions, one for each character.
|
| -/// The transitions are encoded as a 5-bit integer representing the next state
|
| -/// and a 3-bit index into the output table.
|
| -///
|
| -/// For URI scanning, only characters in the range U+0020 through U+007E are
|
| -/// interesting, all characters outside that range are treated the same.
|
| -/// The tables only contain 96 entries, representing that characters in the
|
| -/// interesting range, plus one more to represent all values outside the range.
|
| -/// The character entries are stored in one `Uint8List` per state, with the
|
| -/// transition for a character at position `character ^ 0x60`,
|
| -/// which maps the range U+0020 .. U+007F into positions 0 .. 95.
|
| -/// All remaining characters are mapped to position 31 (`0x7f ^ 0x60`) which
|
| -/// represents the transition for all remaining characters.
|
| -final List<Uint8List> _scannerTables = _createTables();
|
| -
|
| -// ----------------------------------------------------------------------
|
| -// Code to create the URI scanner table.
|
| -
|
| -/// Creates the tables for [_scannerTables] used by [Uri.parse].
|
| -///
|
| -/// See [_scannerTables] for the generated format.
|
| -///
|
| -/// The concrete tables are chosen as a trade-off between the number of states
|
| -/// needed and the precision of the result.
|
| -/// This allows definitely recognizing the general structure of the URI
|
| -/// (presence and location of scheme, user-info, host, port, path, query and
|
| -/// fragment) while at the same time detecting that some components are not
|
| -/// in canonical form (anything containing a `%`, a host-name containing a
|
| -/// capital letter). Since the scanner doesn't know whether something is a
|
| -/// scheme or a path until it sees `:`, or user-info or host until it sees
|
| -/// a `@`, a second pass is needed to validate the scheme and any user-info
|
| -/// is considered non-canonical by default.
|
| -///
|
| -/// The states (starting from [_uriStart]) write positions while scanning
|
| -/// a string from `start` to `end` as follows:
|
| -///
|
| -/// - [_schemeEndIndex]: Should be initialized to `start-1`.
|
| -/// If the URI has a scheme, it is set to the position of the `:` after
|
| -/// the scheme.
|
| -/// - [_hostStartIndex]: Should be initialized to `start - 1`.
|
| -/// If the URI has an authority, it is set to the character before the
|
| -/// host name - either the second `/` in the `//` leading the authority,
|
| -/// or the `@` after a user-info. Comparing this value to the scheme end
|
| -/// position can be used to detect that there is a user-info component.
|
| -/// - [_portStartIndex]: Should be initialized to `start`.
|
| -/// Set to the position of the last `:` in an authority, and unchanged
|
| -/// if there is no authority or no `:` in an authority.
|
| -/// If this position is after the host start, there is a port, otherwise it
|
| -/// is just marking a colon in the user-info component.
|
| -/// - [_pathStartIndex]: Should be initialized to `start`.
|
| -/// Is set to the first path character unless the path is empty.
|
| -/// If the path is empty, the position is either unchanged (`start`) or
|
| -/// the first slash of an authority. So, if the path start is before a
|
| -/// host start or scheme end, the path is empty.
|
| -/// - [_queryStartIndex]: Should be initialized to `end`.
|
| -/// The position of the `?` leading a query if the URI contains a query.
|
| -/// - [_fragmentStartIndex]: Should be initialized to `end`.
|
| -/// The position of the `#` leading a fragment if the URI contains a fragment.
|
| -/// - [_notSimpleIndex]: Should be initialized to `start - 1`.
|
| -/// Set to another value if the URI is considered "not simple".
|
| -/// This is elaborated below.
|
| -///
|
| -/// # Simple URIs
|
| -/// A URI is considered "simple" if it is in a normalized form containing no
|
| -/// escapes. This allows us to skip normalization and checking whether escapes
|
| -/// are valid, and to extract components without worrying about unescaping.
|
| -///
|
| -/// The scanner computes a conservative approximation of being "simple".
|
| -/// It rejects any URI with an escape, with a user-info component (mainly
|
| -/// because they are rare and would increase the number of states in the
|
| -/// scanner significantly), with an IPV6 host or with a capital letter in
|
| -/// the scheme or host name (the scheme is handled in a second scan using
|
| -/// a separate two-state table).
|
| -/// Further, paths containing `..` or `.` path segments are considered
|
| -/// non-simple except for pure relative paths (no scheme or authority) starting
|
| -/// with a sequence of "../" segments.
|
| -///
|
| -/// The transition tables cannot detect a trailing ".." in the path,
|
| -/// followed by a query or fragment, because the segment is not known to be
|
| -/// complete until we are past it, and we then need to store the query/fragment
|
| -/// start instead. This cast is checked manually post-scanning (such a path
|
| -/// needs to be normalized to end in "../", so the URI shouldn't be considered
|
| -/// simple).
|
| -List<Uint8List> _createTables() {
|
| - // TODO(lrn): Use a precomputed table.
|
| -
|
| - // Total number of states for the scanner.
|
| - const int stateCount = 22;
|
| -
|
| - // States used to scan a URI from scratch.
|
| - const int schemeOrPath = 01;
|
| - const int authOrPath = 02;
|
| - const int authOrPathSlash = 03;
|
| - const int uinfoOrHost0 = 04;
|
| - const int uinfoOrHost = 05;
|
| - const int uinfoOrPort0 = 06;
|
| - const int uinfoOrPort = 07;
|
| - const int ipv6Host = 08;
|
| - const int relPathSeg = 09;
|
| - const int pathSeg = 10;
|
| - const int path = 11;
|
| - const int query = 12;
|
| - const int fragment = 13;
|
| - const int schemeOrPathDot = 14;
|
| - const int schemeOrPathDot2 = 15;
|
| - const int relPathSegDot = 16;
|
| - const int relPathSegDot2 = 17;
|
| - const int pathSegDot = 18;
|
| - const int pathSegDot2 = 19;
|
| -
|
| - // States used to validate a scheme after its end position has been found.
|
| - const int scheme0 = _schemeStart;
|
| - const int scheme = 21;
|
| -
|
| - // Constants encoding the write-index for the state transition into the top 5
|
| - // bits of a byte.
|
| - const int schemeEnd = _schemeEndIndex << 5;
|
| - const int hostStart = _hostStartIndex << 5;
|
| - const int portStart = _portStartIndex << 5;
|
| - const int pathStart = _pathStartIndex << 5;
|
| - const int queryStart = _queryStartIndex << 5;
|
| - const int fragmentStart = _fragmentStartIndex << 5;
|
| - const int notSimple = _notSimpleIndex << 5;
|
| -
|
| - /// The `unreserved` characters of RFC 3986.
|
| - const unreserved =
|
| - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~" ;
|
| - /// The `sub-delim` characters of RFC 3986.
|
| - const subDelims = r"!$&'()*+,;=";
|
| - // The `pchar` characters of RFC 3986: characters that may occur in a path,
|
| - // excluding escapes.
|
| - const pchar = "$unreserved$subDelims";
|
| -
|
| - var tables = new List<Uint8List>.generate(stateCount,
|
| - (_) => new Uint8List(96));
|
| -
|
| - // Helper function which initialize the table for [state] with a default
|
| - // transition and returns the table.
|
| - Uint8List build(state, defaultTransition) =>
|
| - tables[state]..fillRange(0, 96, defaultTransition);
|
| -
|
| - // Helper function which sets the transition for each character in [chars]
|
| - // to [transition] in the [target] table.
|
| - // The [chars] string must contain only characters in the U+0020 .. U+007E
|
| - // range.
|
| - void setChars(Uint8List target, String chars, int transition) {
|
| - for (int i = 0; i < chars.length; i++) {
|
| - var char = chars.codeUnitAt(i);
|
| - target[char ^ 0x60] = transition;
|
| - }
|
| - }
|
| -
|
| - /// Helper function which sets the transition for all characters in the
|
| - /// range from `range[0]` to `range[1]` to [transition] in the [target] table.
|
| - ///
|
| - /// The [range] must be a two-character string where both characters are in
|
| - /// the U+0020 .. U+007E range and the former character must have a lower
|
| - /// code point than the latter.
|
| - void setRange(Uint8List target, String range, int transition) {
|
| - for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) {
|
| - target[i ^ 0x60] = transition;
|
| - }
|
| - }
|
| -
|
| - // Create the transitions for each state.
|
| - var b;
|
| -
|
| - // Validate as path, if it is a scheme, we handle it later.
|
| - b = build(_uriStart, schemeOrPath | notSimple);
|
| - setChars(b, pchar, schemeOrPath);
|
| - setChars(b, ".", schemeOrPathDot);
|
| - setChars(b, ":", authOrPath | schemeEnd); // Handle later.
|
| - setChars(b, "/", authOrPathSlash);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(schemeOrPathDot, schemeOrPath | notSimple);
|
| - setChars(b, pchar, schemeOrPath);
|
| - setChars(b, ".", schemeOrPathDot2);
|
| - setChars(b, ':', authOrPath | schemeEnd);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(schemeOrPathDot2, schemeOrPath | notSimple);
|
| - setChars(b, pchar, schemeOrPath);
|
| - setChars(b, "%", schemeOrPath | notSimple);
|
| - setChars(b, ':', authOrPath | schemeEnd);
|
| - setChars(b, "/", relPathSeg);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(schemeOrPath, schemeOrPath | notSimple);
|
| - setChars(b, pchar, schemeOrPath);
|
| - setChars(b, ':', authOrPath | schemeEnd);
|
| - setChars(b, "/", pathSeg);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(authOrPath, path | notSimple);
|
| - setChars(b, pchar, path | pathStart);
|
| - setChars(b, "/", authOrPathSlash | pathStart);
|
| - setChars(b, ".", pathSegDot | pathStart);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(authOrPathSlash, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, "/", uinfoOrHost0 | hostStart);
|
| - setChars(b, ".", pathSegDot);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(uinfoOrHost0, uinfoOrHost | notSimple);
|
| - setChars(b, pchar, uinfoOrHost);
|
| - setRange(b, "AZ", uinfoOrHost | notSimple);
|
| - setChars(b, ":", uinfoOrPort0 | portStart);
|
| - setChars(b, "@", uinfoOrHost0 | hostStart);
|
| - setChars(b, "[", ipv6Host | notSimple);
|
| - setChars(b, "/", pathSeg | pathStart);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(uinfoOrHost, uinfoOrHost | notSimple);
|
| - setChars(b, pchar, uinfoOrHost);
|
| - setRange(b, "AZ", uinfoOrHost | notSimple);
|
| - setChars(b, ":", uinfoOrPort0 | portStart);
|
| - setChars(b, "@", uinfoOrHost0 | hostStart);
|
| - setChars(b, "/", pathSeg | pathStart);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(uinfoOrPort0, uinfoOrPort | notSimple);
|
| - setRange(b, "19", uinfoOrPort);
|
| - setChars(b, "@", uinfoOrHost0 | hostStart);
|
| - setChars(b, "/", pathSeg | pathStart);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(uinfoOrPort, uinfoOrPort | notSimple);
|
| - setRange(b, "09", uinfoOrPort);
|
| - setChars(b, "@", uinfoOrHost0 | hostStart);
|
| - setChars(b, "/", pathSeg | pathStart);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(ipv6Host, ipv6Host);
|
| - setChars(b, "]", uinfoOrHost);
|
| -
|
| - b = build(relPathSeg, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, ".", relPathSegDot);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(relPathSegDot, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, ".", relPathSegDot2);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(relPathSegDot2, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, "/", relPathSeg);
|
| - setChars(b, "?", query | queryStart); // This should be non-simple.
|
| - setChars(b, "#", fragment | fragmentStart); // This should be non-simple.
|
| -
|
| - b = build(pathSeg, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, ".", pathSegDot);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(pathSegDot, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, ".", pathSegDot2);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(pathSegDot2, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, "/", pathSeg | notSimple);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(path, path | notSimple);
|
| - setChars(b, pchar, path);
|
| - setChars(b, "/", pathSeg);
|
| - setChars(b, "?", query | queryStart);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(query, query | notSimple);
|
| - setChars(b, pchar, query);
|
| - setChars(b, "?", query);
|
| - setChars(b, "#", fragment | fragmentStart);
|
| -
|
| - b = build(fragment, fragment | notSimple);
|
| - setChars(b, pchar, fragment);
|
| - setChars(b, "?", fragment);
|
| -
|
| - // A separate two-state validator for lower-case scheme names.
|
| - // Any non-scheme character or upper-case letter is marked as non-simple.
|
| - b = build(scheme0, scheme | notSimple);
|
| - setRange(b, "az", scheme);
|
| -
|
| - b = build(scheme, scheme | notSimple);
|
| - setRange(b, "az", scheme);
|
| - setRange(b, "09", scheme);
|
| - setChars(b, "+-.", scheme);
|
| -
|
| - return tables;
|
| -}
|
| -
|
| -// --------------------------------------------------------------------
|
| -// Code that uses the URI scanner table.
|
| -
|
| -/// Scan a string using the [_scannerTables] state machine.
|
| -///
|
| -/// Scans [uri] from [start] to [end], startig in state [state] and
|
| -/// writing output into [indices].
|
| -///
|
| -/// Returns the final state.
|
| -int _scan(String uri, int start, int end, int state, List<int> indices) {
|
| - var tables = _scannerTables;
|
| - assert(end <= uri.length);
|
| - for (int i = start; i < end; i++) {
|
| - var table = tables[state];
|
| - // Xor with 0x60 to move range 0x20-0x7f into 0x00-0x5f
|
| - int char = uri.codeUnitAt(i) ^ 0x60;
|
| - // Use 0x1f (nee 0x7f) to represent all unhandled characters.
|
| - if (char > 0x5f) char = 0x1f;
|
| - int transition = table[char];
|
| - state = transition & 0x1f;
|
| - indices[transition >> 5] = i;
|
| - }
|
| - return state;
|
| -}
|
| -
|
| -class _SimpleUri implements Uri {
|
| - final String _uri;
|
| - final int _schemeEnd;
|
| - final int _hostStart;
|
| - final int _portStart;
|
| - final int _pathStart;
|
| - final int _queryStart;
|
| - final int _fragmentStart;
|
| - /// The scheme is often used to distinguish URIs.
|
| - /// To make comparisons more efficient, we cache the value, and
|
| - /// canonicalize a few known types.
|
| - String _schemeCache;
|
| - int _hashCodeCache;
|
| -
|
| - _SimpleUri(
|
| - this._uri,
|
| - this._schemeEnd,
|
| - this._hostStart,
|
| - this._portStart,
|
| - this._pathStart,
|
| - this._queryStart,
|
| - this._fragmentStart,
|
| - this._schemeCache);
|
| -
|
| - bool get hasScheme => _schemeEnd > 0;
|
| - bool get hasAuthority => _hostStart > 0;
|
| - bool get hasUserInfo => _hostStart > _schemeEnd + 4;
|
| - bool get hasPort => _hostStart > 0 && _portStart + 1 < _pathStart;
|
| - bool get hasQuery => _queryStart < _fragmentStart;
|
| - bool get hasFragment => _fragmentStart < _uri.length;
|
| -
|
| - bool get _isFile => _schemeEnd == 4 && _uri.startsWith("file");
|
| - bool get _isHttp => _schemeEnd == 4 && _uri.startsWith("http");
|
| - bool get _isHttps => _schemeEnd == 5 && _uri.startsWith("https");
|
| - bool get _isPackage => _schemeEnd == 7 && _uri.startsWith("package");
|
| - bool _isScheme(String scheme) =>
|
| - _schemeEnd == scheme.length && _uri.startsWith(scheme);
|
| -
|
| - bool get hasAbsolutePath => _uri.startsWith("/", _pathStart);
|
| - bool get hasEmptyPath => _pathStart == _queryStart;
|
| -
|
| - bool get isAbsolute => hasScheme && !hasFragment;
|
| -
|
| - String get scheme {
|
| - if (_schemeEnd <= 0) return "";
|
| - if (_schemeCache != null) return _schemeCache;
|
| - if (_isHttp) {
|
| - _schemeCache = "http";
|
| - } else if (_isHttps) {
|
| - _schemeCache = "https";
|
| - } else if (_isFile) {
|
| - _schemeCache = "file";
|
| - } else if (_isPackage) {
|
| - _schemeCache = "package";
|
| - } else {
|
| - _schemeCache = _uri.substring(0, _schemeEnd);
|
| - }
|
| - return _schemeCache;
|
| - }
|
| - String get authority => _hostStart > 0 ?
|
| - _uri.substring(_schemeEnd + 3, _pathStart) : "";
|
| - String get userInfo => (_hostStart > _schemeEnd + 3) ?
|
| - _uri.substring(_schemeEnd + 3, _hostStart - 1) : "";
|
| - String get host =>
|
| - _hostStart > 0 ? _uri.substring(_hostStart, _portStart) : "";
|
| - int get port {
|
| - if (hasPort) return int.parse(_uri.substring(_portStart + 1, _pathStart));
|
| - if (_isHttp) return 80;
|
| - if (_isHttps) return 443;
|
| - return 0;
|
| - }
|
| - String get path =>_uri.substring(_pathStart, _queryStart);
|
| - String get query => (_queryStart < _fragmentStart) ?
|
| - _uri.substring(_queryStart + 1, _fragmentStart) : "";
|
| - String get fragment => (_fragmentStart < _uri.length) ?
|
| - _uri.substring(_fragmentStart + 1) : "";
|
| -
|
| - String get origin {
|
| - // Check original behavior - W3C spec is wonky!
|
| - bool isHttp = _isHttp;
|
| - if (_schemeEnd < 0 || _hostStart == _portStart) {
|
| - throw new StateError("Cannot use origin without a scheme: $this");
|
| - }
|
| - if (!isHttp && !_isHttps) {
|
| - throw new StateError(
|
| - "Origin is only applicable schemes http and https: $this");
|
| - }
|
| - if (_hostStart == _schemeEnd + 3) {
|
| - return _uri.substring(0, _pathStart);
|
| - }
|
| - // Need to drop anon-empty userInfo.
|
| - return _uri.substring(0, _schemeEnd + 3) +
|
| - _uri.substring(_hostStart, _pathStart);
|
| - }
|
| -
|
| - List<String> get pathSegments {
|
| - int start = _pathStart;
|
| - int end = _queryStart;
|
| - if (_uri.startsWith("/", start)) start++;
|
| - if (start == end) return const <String>[];
|
| - List<String> parts = [];
|
| - for (int i = start; i < end; i++) {
|
| - var char = _uri.codeUnitAt(i);
|
| - if (char == _SLASH) {
|
| - parts.add(_uri.substring(start, i));
|
| - start = i + 1;
|
| - }
|
| - }
|
| - parts.add(_uri.substring(start, end));
|
| - return new List<String>.unmodifiable(parts);
|
| - }
|
| -
|
| - Map<String, String> get queryParameters {
|
| - if (!hasQuery) return const <String, String>{};
|
| - return new UnmodifiableMapView<String, String>(
|
| - Uri.splitQueryString(query));
|
| - }
|
| -
|
| - Map<String, List<String>> get queryParametersAll {
|
| - if (!hasQuery) return const <String, List<String>>{};
|
| - Map queryParameterLists = _Uri._splitQueryStringAll(query);
|
| - for (var key in queryParameterLists.keys) {
|
| - queryParameterLists[key] =
|
| - new List<String>.unmodifiable(queryParameterLists[key]);
|
| - }
|
| - return new Map<String, List<String>>.unmodifiable(queryParameterLists);
|
| - }
|
| -
|
| - bool _isPort(String port) {
|
| - int portDigitStart = _portStart + 1;
|
| - return portDigitStart + port.length == _pathStart &&
|
| - _uri.startsWith(port, portDigitStart);
|
| - }
|
| -
|
| - Uri normalizePath() => this;
|
| -
|
| - Uri removeFragment() {
|
| - if (!hasFragment) return this;
|
| - return new _SimpleUri(
|
| - _uri.substring(0, _fragmentStart),
|
| - _schemeEnd, _hostStart, _portStart,
|
| - _pathStart, _queryStart, _fragmentStart, _schemeCache);
|
| - }
|
| -
|
| - Uri replace({String scheme,
|
| - String userInfo,
|
| - String host,
|
| - int port,
|
| - String path,
|
| - Iterable<String> pathSegments,
|
| - String query,
|
| - Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
|
| - String fragment}) {
|
| - bool schemeChanged = false;
|
| - if (scheme != null) {
|
| - scheme = _Uri._makeScheme(scheme, 0, scheme.length);
|
| - schemeChanged = !_isScheme(scheme);
|
| - } else {
|
| - scheme = this.scheme;
|
| - }
|
| - bool isFile = (scheme == "file");
|
| - if (userInfo != null) {
|
| - userInfo = _Uri._makeUserInfo(userInfo, 0, userInfo.length);
|
| - } else if (_hostStart > 0) {
|
| - userInfo = _uri.substring(_schemeEnd + 3, _hostStart);
|
| - } else {
|
| - userInfo = "";
|
| - }
|
| - if (port != null) {
|
| - port = _Uri._makePort(port, scheme);
|
| - } else {
|
| - port = this.hasPort ? this.port : null;
|
| - if (schemeChanged) {
|
| - // The default port might have changed.
|
| - port = _Uri._makePort(port, scheme);
|
| - }
|
| - }
|
| - if (host != null) {
|
| - host = _Uri._makeHost(host, 0, host.length, false);
|
| - } else if (_hostStart > 0) {
|
| - host = _uri.substring(_hostStart, _portStart);
|
| - } else if (userInfo.isNotEmpty || port != null || isFile) {
|
| - host = "";
|
| - }
|
| -
|
| - bool hasAuthority = host != null;
|
| - if (path != null || pathSegments != null) {
|
| - path = _Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments,
|
| - scheme, hasAuthority);
|
| - } else {
|
| - path = _uri.substring(_pathStart, _queryStart);
|
| - if ((isFile || (hasAuthority && !path.isEmpty)) &&
|
| - !path.startsWith('/')) {
|
| - path = "/" + path;
|
| - }
|
| - }
|
| -
|
| - if (query != null || queryParameters != null) {
|
| - query = _Uri._makeQuery(
|
| - query, 0, _stringOrNullLength(query), queryParameters);
|
| - } else if (_queryStart < _fragmentStart) {
|
| - query = _uri.substring(_queryStart, _fragmentStart);
|
| - }
|
| -
|
| - if (fragment != null) {
|
| - fragment = _Uri._makeFragment(fragment, 0, fragment.length);
|
| - } else if (_fragmentStart < _uri.length) {
|
| - fragment = _uri.substring(_fragmentStart + 1);
|
| - }
|
| -
|
| - return new _Uri._internal(
|
| - scheme, userInfo, host, port, path, query, fragment);
|
| - }
|
| -
|
| - Uri resolve(String reference) {
|
| - return resolveUri(Uri.parse(reference));
|
| - }
|
| -
|
| - Uri resolveUri(Uri reference) {
|
| - if (reference is _SimpleUri) {
|
| - return _simpleMerge(this, reference);
|
| - }
|
| - return _toNonSimple().resolveUri(reference);
|
| - }
|
| -
|
| - // Merge two simple URIs. This should always result in a prefix of
|
| - // one concatentated with a suffix of the other, possibly with a `/` in
|
| - // the middle of two merged paths, which is again simple.
|
| - // In a few cases, there might be a need for extra normalization, when
|
| - // resolving on top of a known scheme.
|
| - Uri _simpleMerge(_SimpleUri base, _SimpleUri ref) {
|
| - if (ref.hasScheme) return ref;
|
| - if (ref.hasAuthority) {
|
| - if (!base.hasScheme) return ref;
|
| - bool isSimple = true;
|
| - if (base._isFile) {
|
| - isSimple = !ref.hasEmptyPath;
|
| - } else if (base._isHttp) {
|
| - isSimple = !ref._isPort("80");
|
| - } else if (base._isHttps) {
|
| - isSimple = !ref._isPort("443");
|
| - }
|
| - if (isSimple) {
|
| - var delta = base._schemeEnd + 1;
|
| - var newUri = base._uri.substring(0, base._schemeEnd + 1) +
|
| - ref._uri.substring(ref._schemeEnd + 1);
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - ref._hostStart + delta,
|
| - ref._portStart + delta,
|
| - ref._pathStart + delta,
|
| - ref._queryStart + delta,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - } else {
|
| - // This will require normalization, so use the _Uri implementation.
|
| - return _toNonSimple().resolveUri(ref);
|
| - }
|
| - }
|
| - if (ref.hasEmptyPath) {
|
| - if (ref.hasQuery) {
|
| - int delta = base._queryStart - ref._queryStart;
|
| - var newUri = base._uri.substring(0, base._queryStart) +
|
| - ref._uri.substring(ref._queryStart);
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - base._hostStart,
|
| - base._portStart,
|
| - base._pathStart,
|
| - ref._queryStart + delta,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - }
|
| - if (ref.hasFragment) {
|
| - int delta = base._fragmentStart - ref._fragmentStart;
|
| - var newUri = base._uri.substring(0, base._fragmentStart) +
|
| - ref._uri.substring(ref._fragmentStart);
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - base._hostStart,
|
| - base._portStart,
|
| - base._pathStart,
|
| - base._queryStart,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - }
|
| - return base.removeFragment();
|
| - }
|
| - if (ref.hasAbsolutePath) {
|
| - var delta = base._pathStart - ref._pathStart;
|
| - var newUri = base._uri.substring(0, base._pathStart) +
|
| - ref._uri.substring(ref._pathStart);
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - base._hostStart,
|
| - base._portStart,
|
| - base._pathStart,
|
| - ref._queryStart + delta,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - }
|
| - if (base.hasEmptyPath && base.hasAuthority) {
|
| - // ref has relative non-empty path.
|
| - // Add a "/" in front, then leading "/../" segments are folded to "/".
|
| - int refStart = ref._pathStart;
|
| - while (ref._uri.startsWith("../", refStart)) {
|
| - refStart += 3;
|
| - }
|
| - var delta = base._pathStart - refStart + 1;
|
| - var newUri = "${base._uri.substring(0, base._pathStart)}/"
|
| - "${ref._uri.substring(refStart)}";
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - base._hostStart,
|
| - base._portStart,
|
| - base._pathStart,
|
| - ref._queryStart + delta,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - }
|
| - // Merge paths.
|
| - if (base._uri.startsWith("../", base._pathStart)) {
|
| - // Complex rare case, go slow.
|
| - return _toNonSimple().resolveUri(ref);
|
| - }
|
| -
|
| - // The RFC 3986 algorithm merges the base path without its final segment
|
| - // (anything after the final "/", or everything if the base path doesn't
|
| - // contain any "/"), and the reference path.
|
| - // Then it removes "." and ".." segments using the remove-dot-segment
|
| - // algorithm.
|
| - // This code combines the two steps. It is simplified by knowing that
|
| - // the base path contains no "." or ".." segments, and the reference
|
| - // path can only contain leading ".." segments.
|
| -
|
| - String baseUri = base._uri;
|
| - String refUri = ref._uri;
|
| - int baseStart = base._pathStart;
|
| - int baseEnd = base._queryStart;
|
| - int refStart = ref._pathStart;
|
| - int refEnd = ref._queryStart;
|
| - int backCount = 1;
|
| -
|
| - int slashCount = 0;
|
| -
|
| - // Count leading ".." segments in reference path.
|
| - while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
|
| - refStart += 3;
|
| - backCount += 1;
|
| - }
|
| -
|
| - // Extra slash inserted between base and reference path parts if
|
| - // the base path contains any slashes.
|
| - // (We could use a slash from the base path in most cases, but not if
|
| - // we remove the entire base path).
|
| - String insert = "";
|
| - while (baseEnd > baseStart) {
|
| - baseEnd--;
|
| - int char = baseUri.codeUnitAt(baseEnd);
|
| - if (char == _SLASH) {
|
| - insert = "/";
|
| - backCount--;
|
| - if (backCount == 0) break;
|
| - }
|
| - }
|
| - // If the base URI has no scheme or authority (`_pathStart == 0`)
|
| - // and a relative path, and we reached the beginning of the path,
|
| - // we have a special case.
|
| - if (baseEnd == 0 && !base.hasAbsolutePath) {
|
| - // Non-RFC 3986 behavior when resolving a purely relative path on top of
|
| - // another relative path: Don't make the result absolute.
|
| - insert = "";
|
| - }
|
| -
|
| - var delta = baseEnd - refStart + insert.length;
|
| - var newUri = "${base._uri.substring(0, baseEnd)}$insert"
|
| - "${ref._uri.substring(refStart)}";
|
| -
|
| - return new _SimpleUri(newUri,
|
| - base._schemeEnd,
|
| - base._hostStart,
|
| - base._portStart,
|
| - base._pathStart,
|
| - ref._queryStart + delta,
|
| - ref._fragmentStart + delta,
|
| - base._schemeCache);
|
| - }
|
| -
|
| - String toFilePath({bool windows}) {
|
| - if (_schemeEnd >= 0 && !_isFile) {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a $scheme URI");
|
| - }
|
| - if (_queryStart < _uri.length) {
|
| - if (_queryStart < _fragmentStart) {
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a URI with a query component");
|
| - }
|
| - throw new UnsupportedError(
|
| - "Cannot extract a file path from a URI with a fragment component");
|
| - }
|
| - if (windows == null) windows = _Uri._isWindows;
|
| - return windows ? _Uri._toWindowsFilePath(this) : _toFilePath();
|
| - }
|
| -
|
| - String _toFilePath() {
|
| - if (_hostStart < _portStart) {
|
| - // Has authority and non-empty host.
|
| - throw new UnsupportedError(
|
| - "Cannot extract a non-Windows file path from a file URI "
|
| - "with an authority");
|
| - }
|
| - return this.path;
|
| - }
|
| -
|
| - UriData get data {
|
| - assert(scheme != "data");
|
| - return null;
|
| - }
|
| -
|
| - int get hashCode => _hashCodeCache ??= _uri.hashCode;
|
| -
|
| - bool operator==(Object other) {
|
| - if (identical(this, other)) return true;
|
| - if (other is Uri) return _uri == other.toString();
|
| - return false;
|
| - }
|
| -
|
| - Uri _toNonSimple() {
|
| - return new _Uri._internal(
|
| - this.scheme,
|
| - this.userInfo,
|
| - this.hasAuthority ? this.host: null,
|
| - this.hasPort ? this.port : null,
|
| - this.path,
|
| - this.hasQuery ? this.query : null,
|
| - this.hasFragment ? this.fragment : null
|
| - );
|
| - }
|
| -
|
| - String toString() => _uri;
|
| -}
|
| -
|
| -/// Checks whether [text] starts with "data:" at position [start].
|
| -///
|
| -/// The text must be long enough to allow reading five characters
|
| -/// from the [start] position.
|
| -///
|
| -/// Returns an integer value which is zero if text starts with all-lowercase
|
| -/// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case.
|
| -/// All other values means the text starts with some other character.
|
| -int _startsWithData(String text, int start) {
|
| - // Multiply by 3 to avoid a non-colon character making delta be 0x20.
|
| - int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3;
|
| - delta |= text.codeUnitAt(start) ^ 0x64 /*d*/;
|
| - delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/;
|
| - delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/;
|
| - delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/;
|
| - return delta;
|
| + static const _uricTable = Uri._queryCharTable;
|
| }
|
| -
|
| -/// Helper function returning the length of a string, or `0` for `null`.
|
| -int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
|
|
|