| Index: pkg/dev_compiler/tool/input_sdk/lib/core/uri.dart
|
| diff --git a/pkg/dev_compiler/tool/input_sdk/lib/core/uri.dart b/pkg/dev_compiler/tool/input_sdk/lib/core/uri.dart
|
| deleted file mode 100644
|
| index 1d351068115f9ab3c38c1c7d05fd00dd3cd2563d..0000000000000000000000000000000000000000
|
| --- a/pkg/dev_compiler/tool/input_sdk/lib/core/uri.dart
|
| +++ /dev/null
|
| @@ -1,3354 +0,0 @@
|
| -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -part of dart.core;
|
| -
|
| -/**
|
| - * A parsed URI, such as a URL.
|
| - *
|
| - * **See also:**
|
| - *
|
| - * * [URIs][uris] in the [library tour][libtour]
|
| - * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
|
| - *
|
| - * [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
|
| - */
|
| -class 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.
|
| - */
|
| - // 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.
|
| - *
|
| - * Each component is set through a named argument. Any number of
|
| - * components can be provided. The [path] and [query] components can be set
|
| - * using either of two different named arguments.
|
| - *
|
| - * The scheme component is set through [scheme]. The scheme is
|
| - * normalized to all lowercase letters. If the scheme is omitted or empty,
|
| - * the URI will not have a scheme part.
|
| - *
|
| - * The user info part of the authority component is set through
|
| - * [userInfo]. It defaults to the empty string, which will be omitted
|
| - * from the string representation of the URI.
|
| - *
|
| - * The host part of the authority component is set through
|
| - * [host]. The host can either be a hostname, an IPv4 address or an
|
| - * IPv6 address, contained in '[' and ']'. If the host contains a
|
| - * ':' character, the '[' and ']' are added if not already provided.
|
| - * The host is normalized to all lowercase letters.
|
| - *
|
| - * The port part of the authority component is set through
|
| - * [port].
|
| - * If [port] is omitted or `null`, it implies the default port for
|
| - * the URI's scheme, and is equivalent to passing that port explicitly.
|
| - * The recognized schemes, and their default ports, are "http" (80) and
|
| - * "https" (443). All other schemes are considered as having zero as the
|
| - * default port.
|
| - *
|
| - * If any of `userInfo`, `host` or `port` are provided,
|
| - * the URI has an autority according to [hasAuthority].
|
| - *
|
| - * The path component is set through either [path] or
|
| - * [pathSegments].
|
| - * When [path] is used, it should be a valid URI path,
|
| - * but invalid characters, except the general delimiters ':/@[]?#',
|
| - * will be escaped if necessary.
|
| - * When [pathSegments] is used, each of the provided segments
|
| - * is first percent-encoded and then joined using the forward slash
|
| - * separator.
|
| - *
|
| - * The percent-encoding of the path segments encodes all
|
| - * characters except for the unreserved characters and the following
|
| - * list of characters: `!$&'()*+,;=:@`. If the other components
|
| - * necessitate an absolute path, a leading slash `/` is prepended if
|
| - * not already there.
|
| - *
|
| - * The query component is set through either [query] or [queryParameters].
|
| - * When [query] is used, the provided string should be a valid URI query,
|
| - * but invalid characters, other than general delimiters,
|
| - * will be escaped if necessary.
|
| - * When [queryParameters] is used the query is built from the
|
| - * provided map. Each key and value in the map is percent-encoded
|
| - * and joined using equal and ampersand characters.
|
| - * A value in the map must be either a string, or an [Iterable] of strings,
|
| - * where the latter corresponds to multiple values for the same key.
|
| - *
|
| - * The percent-encoding of the keys and values encodes all characters
|
| - * except for the unreserved characters, and replaces spaces with `+`.
|
| - * If `query` is the empty string, it is equivalent to omitting it.
|
| - * To have an actual empty query part,
|
| - * use an empty map for `queryParameters`.
|
| - *
|
| - * If both `query` and `queryParameters` are omitted or `null`,
|
| - * the URI has no query part.
|
| - *
|
| - * The fragment component is set through [fragment].
|
| - * It should be a valid URI fragment, but invalid characters other than
|
| - * general delimiters, are escaped if necessary.
|
| - * If `fragment` is omitted or `null`, the URI has no fragment part.
|
| - */
|
| - 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 = "";
|
| - }
|
| - 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.
|
| - *
|
| - * Examples:
|
| - *
|
| - * ```
|
| - * // http://example.org/path?q=dart.
|
| - * new Uri.http("google.com", "/search", { "q" : "dart" });
|
| - *
|
| - * // http://user:pass@localhost:8080
|
| - * new Uri.http("user:pass@localhost:8080", "");
|
| - *
|
| - * // http://example.org/a%20b
|
| - * new Uri.http("example.org", "a b");
|
| - *
|
| - * // http://example.org/a%252F
|
| - * new Uri.http("example.org", "/a%2F");
|
| - * ```
|
| - *
|
| - * The `scheme` is always set to `http`.
|
| - *
|
| - * The `userInfo`, `host` and `port` components are set from the
|
| - * [authority] argument. If `authority` is `null` or empty,
|
| - * the created `Uri` has no authority, and isn't directly usable
|
| - * as an HTTP URL, which must have a non-empty host.
|
| - *
|
| - * The `path` component is set from the [unencodedPath]
|
| - * argument. The path passed must not be encoded as this constructor
|
| - * encodes the path.
|
| - *
|
| - * The `query` component is set from the optional [queryParameters]
|
| - * argument.
|
| - */
|
| - factory Uri.http(String authority,
|
| - String unencodedPath,
|
| - [Map<String, String> queryParameters]) {
|
| - return _makeHttpUri("http", authority, unencodedPath, queryParameters);
|
| - }
|
| -
|
| - /**
|
| - * Creates a new `https` URI from authority, path and query.
|
| - *
|
| - * This constructor is the same as [Uri.http] except for the scheme
|
| - * which is set to `https`.
|
| - */
|
| - factory Uri.https(String authority,
|
| - String unencodedPath,
|
| - [Map<String, String> queryParameters]) {
|
| - return _makeHttpUri("https", authority, unencodedPath, queryParameters);
|
| - }
|
| -
|
| - /**
|
| - * 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 {
|
| - if (!hasAuthority) return "";
|
| - var sb = new StringBuffer();
|
| - _writeAuthority(sb);
|
| - return sb.toString();
|
| - }
|
| -
|
| - /**
|
| - * 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 => _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 {
|
| - if (_host == null) return "";
|
| - if (_host.startsWith('[')) {
|
| - return _host.substring(1, _host.length - 1);
|
| - }
|
| - return _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 {
|
| - 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.
|
| - *
|
| - * 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 => _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 => (_query == null) ? "" : _query;
|
| -
|
| - /**
|
| - * Returns the fragment identifier component.
|
| - *
|
| - * 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 [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.
|
| - */
|
| - static Uri parse(String uri, [int start = 0, int end]) {
|
| - // This parsing will not validate percent-encoding, IPv6, etc.
|
| - // When done splitting into parts, it will call, e.g., [_makeFragment]
|
| - // to do the final parsing.
|
| - //
|
| - // Important parts of the RFC 3986 used here:
|
| - // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
| - //
|
| - // hier-part = "//" authority path-abempty
|
| - // / path-absolute
|
| - // / path-rootless
|
| - // / path-empty
|
| - //
|
| - // URI-reference = URI / relative-ref
|
| - //
|
| - // absolute-URI = scheme ":" hier-part [ "?" query ]
|
| - //
|
| - // relative-ref = relative-part [ "?" query ] [ "#" fragment ]
|
| - //
|
| - // relative-part = "//" authority path-abempty
|
| - // / path-absolute
|
| - // / path-noscheme
|
| - // / path-empty
|
| - //
|
| - // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
| - //
|
| - // authority = [ userinfo "@" ] host [ ":" port ]
|
| - // userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
| - // host = IP-literal / IPv4address / reg-name
|
| - // port = *DIGIT
|
| - // reg-name = *( unreserved / pct-encoded / sub-delims )
|
| - //
|
| - // path = path-abempty ; begins with "/" or is empty
|
| - // / path-absolute ; begins with "/" but not "//"
|
| - // / path-noscheme ; begins with a non-colon segment
|
| - // / path-rootless ; begins with a segment
|
| - // / path-empty ; zero characters
|
| - //
|
| - // path-abempty = *( "/" segment )
|
| - // path-absolute = "/" [ segment-nz *( "/" segment ) ]
|
| - // path-noscheme = segment-nz-nc *( "/" segment )
|
| - // path-rootless = segment-nz *( "/" segment )
|
| - // path-empty = 0<pchar>
|
| - //
|
| - // segment = *pchar
|
| - // segment-nz = 1*pchar
|
| - // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
|
| - // ; non-zero-length segment without any colon ":"
|
| - //
|
| - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
| - //
|
| - // query = *( pchar / "/" / "?" )
|
| - //
|
| - // fragment = *( pchar / "/" / "?" )
|
| - const int EOI = -1;
|
| -
|
| - 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;
|
| -
|
| - 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;
|
| - }
|
| - if (char == _SLASH) {
|
| - state = (i == start) ? ALLOW_AUTH : IN_PATH;
|
| - break;
|
| - }
|
| - if (char == _COLON) {
|
| - if (i == start) _fail(uri, start, "Invalid empty scheme");
|
| - scheme = _makeScheme(uri, start, i);
|
| - i++;
|
| - pathStart = i;
|
| - if (i == end) {
|
| - char = EOI;
|
| - state = NOT_IN_PATH;
|
| - } else {
|
| - 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;
|
| - }
|
| - }
|
| - break;
|
| - }
|
| - 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 {
|
| - 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;
|
| - }
|
| - }
|
| - }
|
| -
|
| - 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;
|
| - }
|
| -
|
| - assert(state == NOT_IN_PATH);
|
| - bool hasAuthority = (host != null);
|
| - path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
|
| -
|
| - 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 new Uri._internal(scheme,
|
| - userinfo,
|
| - host,
|
| - port,
|
| - path,
|
| - query,
|
| - fragment);
|
| - }
|
| -
|
| - // Report a parse failure.
|
| - static void _fail(String uri, int index, String message) {
|
| - throw new FormatException(message, uri, index);
|
| - }
|
| -
|
| - static Uri _makeHttpUri(String scheme,
|
| - String authority,
|
| - String unencodedPath,
|
| - Map<String, String> queryParameters) {
|
| - var userInfo = "";
|
| - var host = null;
|
| - var port = null;
|
| -
|
| - if (authority != null && authority.isNotEmpty) {
|
| - var hostStart = 0;
|
| - // Split off the user info.
|
| - bool hasUserInfo = false;
|
| - for (int i = 0; i < authority.length; i++) {
|
| - if (authority.codeUnitAt(i) == _AT_SIGN) {
|
| - hasUserInfo = true;
|
| - userInfo = authority.substring(0, i);
|
| - hostStart = i + 1;
|
| - break;
|
| - }
|
| - }
|
| - var hostEnd = hostStart;
|
| - if (hostStart < authority.length &&
|
| - authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
|
| - // IPv6 host.
|
| - for (; hostEnd < authority.length; hostEnd++) {
|
| - if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
|
| - }
|
| - if (hostEnd == authority.length) {
|
| - throw new FormatException("Invalid IPv6 host entry.",
|
| - authority, hostStart);
|
| - }
|
| - parseIPv6Address(authority, hostStart + 1, hostEnd);
|
| - hostEnd++; // Skip the closing bracket.
|
| - if (hostEnd != authority.length &&
|
| - authority.codeUnitAt(hostEnd) != _COLON) {
|
| - throw new FormatException("Invalid end of authority",
|
| - authority, hostEnd);
|
| - }
|
| - }
|
| - // Split host and port.
|
| - bool hasPort = false;
|
| - for (; hostEnd < authority.length; hostEnd++) {
|
| - if (authority.codeUnitAt(hostEnd) == _COLON) {
|
| - var portString = authority.substring(hostEnd + 1);
|
| - // We allow the empty port - falling back to initial value.
|
| - if (portString.isNotEmpty) port = int.parse(portString);
|
| - break;
|
| - }
|
| - }
|
| - host = authority.substring(hostStart, hostEnd);
|
| - }
|
| - return new Uri(scheme: scheme,
|
| - userInfo: userInfo,
|
| - host: host,
|
| - port: port,
|
| - pathSegments: unencodedPath.split("/"),
|
| - queryParameters: 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:
|
| - *
|
| - * ```
|
| - * // 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);
|
| - }
|
| -
|
| - /**
|
| - * 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;
|
| -
|
| - external static bool get _isWindows;
|
| -
|
| - static _checkNonWindowsPathReservedCharacters(List<String> segments,
|
| - bool argumentError) {
|
| - segments.forEach((segment) {
|
| - if (segment.contains("/")) {
|
| - if (argumentError) {
|
| - throw new ArgumentError("Illegal path character $segment");
|
| - } else {
|
| - throw new UnsupportedError("Illegal path character $segment");
|
| - }
|
| - }
|
| - });
|
| - }
|
| -
|
| - static _checkWindowsPathReservedCharacters(List<String> segments,
|
| - bool argumentError,
|
| - [int firstSegment = 0]) {
|
| - for (var segment in segments.skip(firstSegment)) {
|
| - if (segment.contains(new RegExp(r'["*/:<>?\\|]'))) {
|
| - if (argumentError) {
|
| - throw new ArgumentError("Illegal character in path");
|
| - } else {
|
| - throw new UnsupportedError("Illegal character in path");
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - static _checkWindowsDriveLetter(int charCode, bool argumentError) {
|
| - if ((_UPPER_CASE_A <= charCode && charCode <= _UPPER_CASE_Z) ||
|
| - (_LOWER_CASE_A <= charCode && charCode <= _LOWER_CASE_Z)) {
|
| - return;
|
| - }
|
| - if (argumentError) {
|
| - throw new ArgumentError("Illegal drive letter " +
|
| - new String.fromCharCode(charCode));
|
| - } else {
|
| - throw new UnsupportedError("Illegal drive letter " +
|
| - new String.fromCharCode(charCode));
|
| - }
|
| - }
|
| -
|
| - static _makeFileUri(String path, bool slashTerminated) {
|
| - const String sep = "/";
|
| - var segments = path.split(sep);
|
| - if (slashTerminated &&
|
| - segments.isNotEmpty &&
|
| - segments.last.isNotEmpty) {
|
| - segments.add(""); // Extra separator at end.
|
| - }
|
| - if (path.startsWith(sep)) {
|
| - // Absolute file:// URI.
|
| - return new Uri(scheme: "file", pathSegments: segments);
|
| - } else {
|
| - // Relative URI.
|
| - return new Uri(pathSegments: segments);
|
| - }
|
| - }
|
| -
|
| - static _makeWindowsFileUrl(String path, bool slashTerminated) {
|
| - if (path.startsWith(r"\\?\")) {
|
| - if (path.startsWith(r"UNC\", 4)) {
|
| - path = path.replaceRange(0, 7, r'\');
|
| - } else {
|
| - path = path.substring(4);
|
| - if (path.length < 3 ||
|
| - path.codeUnitAt(1) != _COLON ||
|
| - path.codeUnitAt(2) != _BACKSLASH) {
|
| - throw new ArgumentError(
|
| - r"Windows paths with \\?\ prefix must be absolute");
|
| - }
|
| - }
|
| - } else {
|
| - path = path.replaceAll("/", r'\');
|
| - }
|
| - const String sep = r'\';
|
| - if (path.length > 1 && path.codeUnitAt(1) == _COLON) {
|
| - _checkWindowsDriveLetter(path.codeUnitAt(0), true);
|
| - if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
|
| - throw new ArgumentError(
|
| - "Windows paths with drive letter must be absolute");
|
| - }
|
| - // Absolute file://C:/ URI.
|
| - var pathSegments = path.split(sep);
|
| - if (slashTerminated &&
|
| - pathSegments.last.isNotEmpty) {
|
| - pathSegments.add(""); // Extra separator at end.
|
| - }
|
| - _checkWindowsPathReservedCharacters(pathSegments, true, 1);
|
| - return new Uri(scheme: "file", pathSegments: pathSegments);
|
| - }
|
| -
|
| - if (path.startsWith(sep)) {
|
| - if (path.startsWith(sep, 1)) {
|
| - // Absolute file:// URI with host.
|
| - int pathStart = path.indexOf(r'\', 2);
|
| - String hostPart =
|
| - (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart);
|
| - String pathPart =
|
| - (pathStart < 0) ? "" : path.substring(pathStart + 1);
|
| - var pathSegments = pathPart.split(sep);
|
| - _checkWindowsPathReservedCharacters(pathSegments, true);
|
| - if (slashTerminated &&
|
| - pathSegments.last.isNotEmpty) {
|
| - pathSegments.add(""); // Extra separator at end.
|
| - }
|
| - return new Uri(
|
| - scheme: "file", host: hostPart, pathSegments: pathSegments);
|
| - } else {
|
| - // Absolute file:// URI.
|
| - var pathSegments = path.split(sep);
|
| - if (slashTerminated &&
|
| - pathSegments.last.isNotEmpty) {
|
| - pathSegments.add(""); // Extra separator at end.
|
| - }
|
| - _checkWindowsPathReservedCharacters(pathSegments, true);
|
| - return new Uri(scheme: "file", pathSegments: pathSegments);
|
| - }
|
| - } else {
|
| - // Relative URI.
|
| - var pathSegments = path.split(sep);
|
| - _checkWindowsPathReservedCharacters(pathSegments, true);
|
| - if (slashTerminated &&
|
| - pathSegments.isNotEmpty &&
|
| - pathSegments.last.isNotEmpty) {
|
| - pathSegments.add(""); // Extra separator at end.
|
| - }
|
| - return new Uri(pathSegments: pathSegments);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * 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}) {
|
| - // Set to true if the scheme has (potentially) changed.
|
| - // In that case, the default port may also have changed and we need
|
| - // to check even the existing port.
|
| - bool schemeChanged = false;
|
| - if (scheme != null) {
|
| - scheme = _makeScheme(scheme, 0, scheme.length);
|
| - schemeChanged = true;
|
| - } else {
|
| - scheme = this.scheme;
|
| - }
|
| - bool isFile = (scheme == "file");
|
| - if (userInfo != null) {
|
| - userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
|
| - } else {
|
| - userInfo = this._userInfo;
|
| - }
|
| - if (port != null) {
|
| - port = _makePort(port, scheme);
|
| - } else {
|
| - port = this._port;
|
| - if (schemeChanged) {
|
| - // The default port might have changed.
|
| - port = _makePort(port, scheme);
|
| - }
|
| - }
|
| - if (host != null) {
|
| - host = _makeHost(host, 0, host.length, false);
|
| - } else if (this.hasAuthority) {
|
| - host = this._host;
|
| - } else if (userInfo.isNotEmpty || port != null || isFile) {
|
| - host = "";
|
| - }
|
| -
|
| - bool hasAuthority = host != null;
|
| - if (path != null || pathSegments != null) {
|
| - path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
|
| - scheme, hasAuthority);
|
| - } else {
|
| - path = this._path;
|
| - if ((isFile || (hasAuthority && !path.isEmpty)) &&
|
| - !path.startsWith('/')) {
|
| - path = "/" + path;
|
| - }
|
| - }
|
| -
|
| - if (query != null || queryParameters != null) {
|
| - query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
|
| - } else {
|
| - query = this._query;
|
| - }
|
| -
|
| - if (fragment != null) {
|
| - fragment = _makeFragment(fragment, 0, fragment.length);
|
| - } else {
|
| - fragment = this._fragment;
|
| - }
|
| -
|
| - 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,
|
| - _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;
|
| -
|
| - var pathToSplit = path;
|
| - if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) {
|
| - pathToSplit = pathToSplit.substring(1);
|
| - }
|
| - result = (pathToSplit == "")
|
| - ? const<String>[]
|
| - : new List<String>.unmodifiable(
|
| - pathToSplit.split("/").map(Uri.decodeComponent));
|
| - _pathSegments = result;
|
| - 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>(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);
|
| - for (var key in queryParameterLists.keys) {
|
| - queryParameterLists[key] =
|
| - new List<String>.unmodifiable(queryParameterLists[key]);
|
| - }
|
| - _queryParameterLists =
|
| - new Map<String, List<String>>.unmodifiable(queryParameterLists);
|
| - }
|
| - 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;
|
| - return this.replace(path: path);
|
| - }
|
| -
|
| - static int _makePort(int port, String scheme) {
|
| - // Perform scheme specific normalization.
|
| - if (port != null && port == _defaultPort(scheme)) return null;
|
| - return port;
|
| - }
|
| -
|
| - /**
|
| - * Check and normalize a host name.
|
| - *
|
| - * If the host name starts and ends with '[' and ']', it is considered an
|
| - * IPv6 address. If [strictIPv6] is false, the address is also considered
|
| - * an IPv6 address if it contains any ':' character.
|
| - *
|
| - * If it is not an IPv6 address, it is case- and escape-normalized.
|
| - * This escapes all characters not valid in a reg-name,
|
| - * and converts all non-escape upper-case letters to lower-case.
|
| - */
|
| - static String _makeHost(String host, int start, int end, bool strictIPv6) {
|
| - // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
|
| - if (host == null) return null;
|
| - if (start == end) return "";
|
| - // Host is an IPv6 address if it starts with '[' or contains a colon.
|
| - if (host.codeUnitAt(start) == _LEFT_BRACKET) {
|
| - if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
|
| - _fail(host, start, 'Missing end `]` to match `[` in host');
|
| - }
|
| - parseIPv6Address(host, start + 1, end - 1);
|
| - // RFC 5952 requires hex digits to be lower case.
|
| - return host.substring(start, end).toLowerCase();
|
| - }
|
| - if (!strictIPv6) {
|
| - // TODO(lrn): skip if too short to be a valid IPv6 address?
|
| - for (int i = start; i < end; i++) {
|
| - if (host.codeUnitAt(i) == _COLON) {
|
| - parseIPv6Address(host, start, end);
|
| - return '[$host]';
|
| - }
|
| - }
|
| - }
|
| - return _normalizeRegName(host, start, end);
|
| - }
|
| -
|
| - static bool _isRegNameChar(int char) {
|
| - return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
|
| - }
|
| -
|
| - /**
|
| - * Validates and does case- and percent-encoding normalization.
|
| - *
|
| - * The [host] must be an RFC3986 "reg-name". It is converted
|
| - * to lower case, and percent escapes are converted to either
|
| - * lower case unreserved characters or upper case escapes.
|
| - */
|
| - static String _normalizeRegName(String host, int start, int end) {
|
| - StringBuffer buffer;
|
| - int sectionStart = start;
|
| - int index = start;
|
| - // Whether all characters between sectionStart and index are normalized,
|
| - bool isNormalized = true;
|
| -
|
| - while (index < end) {
|
| - int char = host.codeUnitAt(index);
|
| - if (char == _PERCENT) {
|
| - // The _regNameTable contains "%", so we check that first.
|
| - String replacement = _normalizeEscape(host, index, true);
|
| - if (replacement == null && isNormalized) {
|
| - index += 3;
|
| - continue;
|
| - }
|
| - if (buffer == null) buffer = new StringBuffer();
|
| - String slice = host.substring(sectionStart, index);
|
| - if (!isNormalized) slice = slice.toLowerCase();
|
| - buffer.write(slice);
|
| - int sourceLength = 3;
|
| - if (replacement == null) {
|
| - replacement = host.substring(index, index + 3);
|
| - } else if (replacement == "%") {
|
| - replacement = "%25";
|
| - sourceLength = 1;
|
| - }
|
| - buffer.write(replacement);
|
| - index += sourceLength;
|
| - sectionStart = index;
|
| - isNormalized = true;
|
| - } else if (_isRegNameChar(char)) {
|
| - if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
|
| - // Put initial slice in buffer and continue in non-normalized mode
|
| - if (buffer == null) buffer = new StringBuffer();
|
| - if (sectionStart < index) {
|
| - buffer.write(host.substring(sectionStart, index));
|
| - sectionStart = index;
|
| - }
|
| - isNormalized = false;
|
| - }
|
| - index++;
|
| - } else if (_isGeneralDelimiter(char)) {
|
| - _fail(host, index, "Invalid character");
|
| - } else {
|
| - int sourceLength = 1;
|
| - if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
|
| - int tail = host.codeUnitAt(index + 1);
|
| - if ((tail & 0xFC00) == 0xDC00) {
|
| - char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
|
| - sourceLength = 2;
|
| - }
|
| - }
|
| - if (buffer == null) buffer = new StringBuffer();
|
| - String slice = host.substring(sectionStart, index);
|
| - if (!isNormalized) slice = slice.toLowerCase();
|
| - buffer.write(slice);
|
| - buffer.write(_escapeChar(char));
|
| - index += sourceLength;
|
| - sectionStart = index;
|
| - }
|
| - }
|
| - if (buffer == null) return host.substring(start, end);
|
| - if (sectionStart < end) {
|
| - String slice = host.substring(sectionStart, end);
|
| - if (!isNormalized) slice = slice.toLowerCase();
|
| - buffer.write(slice);
|
| - }
|
| - return buffer.toString();
|
| - }
|
| -
|
| - /**
|
| - * Validates scheme characters and does case-normalization.
|
| - *
|
| - * Schemes are converted to lower case. They cannot contain escapes.
|
| - */
|
| - static String _makeScheme(String scheme, int start, int end) {
|
| - if (start == end) return "";
|
| - final int firstCodeUnit = scheme.codeUnitAt(start);
|
| - if (!_isAlphabeticCharacter(firstCodeUnit)) {
|
| - _fail(scheme, start, "Scheme not starting with alphabetic character");
|
| - }
|
| - bool containsUpperCase = false;
|
| - for (int i = start; i < end; i++) {
|
| - final int codeUnit = scheme.codeUnitAt(i);
|
| - if (!_isSchemeCharacter(codeUnit)) {
|
| - _fail(scheme, i, "Illegal scheme character");
|
| - }
|
| - if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
|
| - containsUpperCase = true;
|
| - }
|
| - }
|
| - scheme = scheme.substring(start, end);
|
| - if (containsUpperCase) scheme = scheme.toLowerCase();
|
| - return scheme;
|
| - }
|
| -
|
| - static String _makeUserInfo(String userInfo, int start, int end) {
|
| - if (userInfo == null) return "";
|
| - return _normalize(userInfo, start, end, _userinfoTable);
|
| - }
|
| -
|
| - static String _makePath(String path, int start, int end,
|
| - Iterable<String> pathSegments,
|
| - String scheme,
|
| - bool hasAuthority) {
|
| - bool isFile = (scheme == "file");
|
| - bool ensureLeadingSlash = isFile || hasAuthority;
|
| - if (path == null && pathSegments == null) return isFile ? "/" : "";
|
| - if (path != null && pathSegments != null) {
|
| - throw new ArgumentError('Both path and pathSegments specified');
|
| - }
|
| - var result;
|
| - if (path != null) {
|
| - result = _normalize(path, start, end, _pathCharOrSlashTable);
|
| - } else {
|
| - result = pathSegments.map((s) =>
|
| - _uriEncode(_pathCharTable, s, UTF8, false)).join("/");
|
| - }
|
| - if (result.isEmpty) {
|
| - if (isFile) return "/";
|
| - } else if (ensureLeadingSlash && !result.startsWith('/')) {
|
| - result = "/" + result;
|
| - }
|
| - result = _normalizePath(result, scheme, hasAuthority);
|
| - return result;
|
| - }
|
| -
|
| - /// Performs path normalization (remove dot segments) on a path.
|
| - ///
|
| - /// If the URI has neither scheme nor authority, it's considered a
|
| - /// "pure path" and normalization won't remove leading ".." segments.
|
| - /// Otherwise it follows the RFC 3986 "remove dot segments" algorithm.
|
| - static String _normalizePath(String path, String scheme, bool hasAuthority) {
|
| - if (scheme.isEmpty && !hasAuthority && !path.startsWith('/')) {
|
| - return _normalizeRelativePath(path);
|
| - }
|
| - return _removeDotSegments(path);
|
| - }
|
| -
|
| - static String _makeQuery(
|
| - String query, int start, int end,
|
| - Map<String, dynamic/*String|Iterable<String>*/> queryParameters) {
|
| - if (query == null && queryParameters == null) return null;
|
| - if (query != null && queryParameters != null) {
|
| - throw new ArgumentError('Both query and queryParameters specified');
|
| - }
|
| - if (query != null) return _normalize(query, start, end, _queryCharTable);
|
| -
|
| - var result = new StringBuffer();
|
| - var separator = "";
|
| -
|
| - void writeParameter(String key, String value) {
|
| - result.write(separator);
|
| - separator = "&";
|
| - result.write(Uri.encodeQueryComponent(key));
|
| - if (value != null && value.isNotEmpty) {
|
| - result.write("=");
|
| - result.write(Uri.encodeQueryComponent(value));
|
| - }
|
| - }
|
| -
|
| - queryParameters.forEach((key, value) {
|
| - if (value == null || value is String) {
|
| - writeParameter(key, value);
|
| - } else {
|
| - Iterable values = value;
|
| - for (String value in values) {
|
| - writeParameter(key, value);
|
| - }
|
| - }
|
| - });
|
| - return result.toString();
|
| - }
|
| -
|
| - static String _makeFragment(String fragment, int start, int end) {
|
| - if (fragment == null) return null;
|
| - return _normalize(fragment, start, end, _queryCharTable);
|
| - }
|
| -
|
| - static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
|
| -
|
| - /**
|
| - * Performs RFC 3986 Percent-Encoding Normalization.
|
| - *
|
| - * Returns a replacement string that should be replace the original escape.
|
| - * Returns null if no replacement is necessary because the escape is
|
| - * not for an unreserved character and is already non-lower-case.
|
| - *
|
| - * Returns "%" if the escape is invalid (not two valid hex digits following
|
| - * the percent sign). The calling code should replace the percent
|
| - * sign with "%25", but leave the following two characters unmodified.
|
| - *
|
| - * If [lowerCase] is true, a single character returned is always lower case,
|
| - */
|
| - static String _normalizeEscape(String source, int index, bool lowerCase) {
|
| - assert(source.codeUnitAt(index) == _PERCENT);
|
| - if (index + 2 >= source.length) {
|
| - return "%"; // Marks the escape as invalid.
|
| - }
|
| - int firstDigit = source.codeUnitAt(index + 1);
|
| - int secondDigit = source.codeUnitAt(index + 2);
|
| - int firstDigitValue = _parseHexDigit(firstDigit);
|
| - int secondDigitValue = _parseHexDigit(secondDigit);
|
| - if (firstDigitValue < 0 || secondDigitValue < 0) {
|
| - return "%"; // Marks the escape as invalid.
|
| - }
|
| - int value = firstDigitValue * 16 + secondDigitValue;
|
| - if (_isUnreservedChar(value)) {
|
| - if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
|
| - value |= 0x20;
|
| - }
|
| - return new String.fromCharCode(value);
|
| - }
|
| - if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
|
| - // Either digit is lower case.
|
| - return source.substring(index, index + 3).toUpperCase();
|
| - }
|
| - // Escape is retained, and is already non-lower case, so return null to
|
| - // represent "no replacement necessary".
|
| - return null;
|
| - }
|
| -
|
| - // Converts a UTF-16 code-unit to its value as a hex digit.
|
| - // Returns -1 for non-hex digits.
|
| - static int _parseHexDigit(int char) {
|
| - int digit = char ^ Uri._ZERO;
|
| - if (digit <= 9) return digit;
|
| - int lowerCase = char | 0x20;
|
| - if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
|
| - return lowerCase - (_LOWER_CASE_A - 10);
|
| - }
|
| - return -1;
|
| - }
|
| -
|
| - static String _escapeChar(int char) {
|
| - assert(char <= 0x10ffff); // It's a valid unicode code point.
|
| - List<int> codeUnits;
|
| - if (char < 0x80) {
|
| - // ASCII, a single percent encoded sequence.
|
| - codeUnits = new List(3);
|
| - codeUnits[0] = _PERCENT;
|
| - codeUnits[1] = _hexDigits.codeUnitAt(char >> 4);
|
| - codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf);
|
| - } else {
|
| - // Do UTF-8 encoding of character, then percent encode bytes.
|
| - int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
|
| - int encodedBytes = 2;
|
| - if (char > 0x7ff) {
|
| - flag = 0xe0;
|
| - encodedBytes = 3;
|
| - if (char > 0xffff) {
|
| - encodedBytes = 4;
|
| - flag = 0xf0;
|
| - }
|
| - }
|
| - codeUnits = new List(3 * encodedBytes);
|
| - int index = 0;
|
| - while (--encodedBytes >= 0) {
|
| - int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
|
| - codeUnits[index] = _PERCENT;
|
| - codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4);
|
| - codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf);
|
| - index += 3;
|
| - flag = 0x80; // Following bytes have only high bit set.
|
| - }
|
| - }
|
| - return new String.fromCharCodes(codeUnits);
|
| - }
|
| -
|
| - /**
|
| - * Runs through component checking that each character is valid and
|
| - * normalize percent escapes.
|
| - *
|
| - * Uses [charTable] to check if a non-`%` character is allowed.
|
| - * Each `%` character must be followed by two hex digits.
|
| - * If the hex-digits are lower case letters, they are converted to
|
| - * upper case.
|
| - */
|
| - static String _normalize(String component, int start, int end,
|
| - List<int> charTable) {
|
| - StringBuffer buffer;
|
| - int sectionStart = start;
|
| - int index = start;
|
| - // Loop while characters are valid and escapes correct and upper-case.
|
| - while (index < end) {
|
| - int char = component.codeUnitAt(index);
|
| - if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
|
| - index++;
|
| - } else {
|
| - String replacement;
|
| - int sourceLength;
|
| - if (char == _PERCENT) {
|
| - replacement = _normalizeEscape(component, index, false);
|
| - // Returns null if we should keep the existing escape.
|
| - if (replacement == null) {
|
| - index += 3;
|
| - continue;
|
| - }
|
| - // Returns "%" if we should escape the existing percent.
|
| - if ("%" == replacement) {
|
| - replacement = "%25";
|
| - sourceLength = 1;
|
| - } else {
|
| - sourceLength = 3;
|
| - }
|
| - } else if (_isGeneralDelimiter(char)) {
|
| - _fail(component, index, "Invalid character");
|
| - } else {
|
| - sourceLength = 1;
|
| - if ((char & 0xFC00) == 0xD800) {
|
| - // Possible lead surrogate.
|
| - if (index + 1 < end) {
|
| - int tail = component.codeUnitAt(index + 1);
|
| - if ((tail & 0xFC00) == 0xDC00) {
|
| - // Tail surrogat.
|
| - sourceLength = 2;
|
| - char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
|
| - }
|
| - }
|
| - }
|
| - replacement = _escapeChar(char);
|
| - }
|
| - if (buffer == null) buffer = new StringBuffer();
|
| - buffer.write(component.substring(sectionStart, index));
|
| - buffer.write(replacement);
|
| - index += sourceLength;
|
| - sectionStart = index;
|
| - }
|
| - }
|
| - if (buffer == null) {
|
| - // Makes no copy if start == 0 and end == component.length.
|
| - return component.substring(start, end);
|
| - }
|
| - if (sectionStart < end) {
|
| - buffer.write(component.substring(sectionStart, end));
|
| - }
|
| - return buffer.toString();
|
| - }
|
| -
|
| - static bool _isSchemeCharacter(int ch) {
|
| - return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
|
| - }
|
| -
|
| - static bool _isGeneralDelimiter(int ch) {
|
| - return ch <= _RIGHT_BRACKET &&
|
| - ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
|
| - }
|
| -
|
| - /**
|
| - * Returns whether the URI is absolute.
|
| - */
|
| - bool get isAbsolute => scheme != "" && fragment == "";
|
| -
|
| - String _mergePaths(String base, String reference) {
|
| - // Optimize for the case: absolute base, reference beginning with "../".
|
| - int backCount = 0;
|
| - int refStart = 0;
|
| - // Count number of "../" at beginning of reference.
|
| - while (reference.startsWith("../", refStart)) {
|
| - refStart += 3;
|
| - backCount++;
|
| - }
|
| -
|
| - // Drop last segment - everything after last '/' of base.
|
| - int baseEnd = base.lastIndexOf('/');
|
| - // Drop extra segments for each leading "../" of reference.
|
| - while (baseEnd > 0 && backCount > 0) {
|
| - int newEnd = base.lastIndexOf('/', baseEnd - 1);
|
| - if (newEnd < 0) {
|
| - break;
|
| - }
|
| - int delta = baseEnd - newEnd;
|
| - // If we see a "." or ".." segment in base, stop here and let
|
| - // _removeDotSegments handle it.
|
| - if ((delta == 2 || delta == 3) &&
|
| - base.codeUnitAt(newEnd + 1) == _DOT &&
|
| - (delta == 2 || base.codeUnitAt(newEnd + 2) == _DOT)) {
|
| - break;
|
| - }
|
| - baseEnd = newEnd;
|
| - backCount--;
|
| - }
|
| - return base.replaceRange(baseEnd + 1, null,
|
| - reference.substring(refStart - 3 * backCount));
|
| - }
|
| -
|
| - /// Make a guess at whether a path contains a `..` or `.` segment.
|
| - ///
|
| - /// This is a primitive test that can cause false positives.
|
| - /// It's only used to avoid a more expensive operation in the case where
|
| - /// it's not necessary.
|
| - static bool _mayContainDotSegments(String path) {
|
| - if (path.startsWith('.')) return true;
|
| - int index = path.indexOf("/.");
|
| - return index != -1;
|
| - }
|
| -
|
| - /// Removes '.' and '..' segments from a path.
|
| - ///
|
| - /// Follows the RFC 2986 "remove dot segments" algorithm.
|
| - /// This algorithm is only used on paths of URIs with a scheme,
|
| - /// and it treats the path as if it is absolute (leading '..' are removed).
|
| - static String _removeDotSegments(String path) {
|
| - if (!_mayContainDotSegments(path)) return path;
|
| - assert(path.isNotEmpty); // An empty path would not have dot segments.
|
| - List<String> output = [];
|
| - bool appendSlash = false;
|
| - for (String segment in path.split("/")) {
|
| - appendSlash = false;
|
| - if (segment == "..") {
|
| - if (output.isNotEmpty) {
|
| - output.removeLast();
|
| - if (output.isEmpty) {
|
| - output.add("");
|
| - }
|
| - }
|
| - appendSlash = true;
|
| - } else if ("." == segment) {
|
| - appendSlash = true;
|
| - } else {
|
| - output.add(segment);
|
| - }
|
| - }
|
| - if (appendSlash) output.add("");
|
| - return output.join("/");
|
| - }
|
| -
|
| - /// Removes all `.` segments and any non-leading `..` segments.
|
| - ///
|
| - /// Removing the ".." from a "bar/foo/.." sequence results in "bar/"
|
| - /// (trailing "/"). If the entire path is removed (because it contains as
|
| - /// many ".." segments as real segments), the result is "./".
|
| - /// This is different from an empty string, which represents "no path",
|
| - /// when you resolve it against a base URI with a path with a non-empty
|
| - /// final segment.
|
| - static String _normalizeRelativePath(String path) {
|
| - assert(!path.startsWith('/')); // Only get called for relative paths.
|
| - if (!_mayContainDotSegments(path)) return path;
|
| - assert(path.isNotEmpty); // An empty path would not have dot segments.
|
| - List<String> output = [];
|
| - bool appendSlash = false;
|
| - for (String segment in path.split("/")) {
|
| - appendSlash = false;
|
| - if (".." == segment) {
|
| - if (!output.isEmpty && output.last != "..") {
|
| - output.removeLast();
|
| - appendSlash = true;
|
| - } else {
|
| - output.add("..");
|
| - }
|
| - } else if ("." == segment) {
|
| - appendSlash = true;
|
| - } else {
|
| - output.add(segment);
|
| - }
|
| - }
|
| - if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
|
| - return "./";
|
| - }
|
| - if (appendSlash || output.last == '..') output.add("");
|
| - 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;
|
| - String targetUserInfo = "";
|
| - String targetHost;
|
| - int targetPort;
|
| - String targetPath;
|
| - String targetQuery;
|
| - if (reference.scheme.isNotEmpty) {
|
| - targetScheme = reference.scheme;
|
| - if (reference.hasAuthority) {
|
| - targetUserInfo = reference.userInfo;
|
| - targetHost = reference.host;
|
| - targetPort = reference.hasPort ? reference.port : null;
|
| - }
|
| - targetPath = _removeDotSegments(reference.path);
|
| - if (reference.hasQuery) {
|
| - targetQuery = reference.query;
|
| - }
|
| - } else {
|
| - targetScheme = this.scheme;
|
| - if (reference.hasAuthority) {
|
| - targetUserInfo = reference.userInfo;
|
| - targetHost = reference.host;
|
| - targetPort = _makePort(reference.hasPort ? reference.port : null,
|
| - targetScheme);
|
| - targetPath = _removeDotSegments(reference.path);
|
| - if (reference.hasQuery) targetQuery = reference.query;
|
| - } else {
|
| - targetUserInfo = this._userInfo;
|
| - targetHost = this._host;
|
| - targetPort = this._port;
|
| - if (reference.path == "") {
|
| - targetPath = this._path;
|
| - if (reference.hasQuery) {
|
| - targetQuery = reference.query;
|
| - } else {
|
| - targetQuery = this._query;
|
| - }
|
| - } else {
|
| - if (reference.hasAbsolutePath) {
|
| - targetPath = _removeDotSegments(reference.path);
|
| - } else {
|
| - // This is the RFC 3986 behavior for merging.
|
| - if (this.hasEmptyPath) {
|
| - if (!this.hasScheme && !this.hasAuthority) {
|
| - // Keep the path relative if no scheme or authority.
|
| - targetPath = reference.path;
|
| - } else {
|
| - // Add path normalization on top of RFC algorithm.
|
| - targetPath = _removeDotSegments("/" + reference.path);
|
| - }
|
| - } else {
|
| - var mergedPath = _mergePaths(this._path, reference.path);
|
| - if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
|
| - targetPath = _removeDotSegments(mergedPath);
|
| - } else {
|
| - // 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);
|
| - }
|
| - }
|
| - }
|
| - if (reference.hasQuery) targetQuery = reference.query;
|
| - }
|
| - }
|
| - }
|
| - String fragment = reference.hasFragment ? reference.fragment : null;
|
| - 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);
|
| - }
|
| -
|
| - /**
|
| - * 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);
|
| - }
|
| -
|
| - /**
|
| - * 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);
|
| - }
|
| -
|
| - /**
|
| - * 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);
|
| - }
|
| -
|
| - /**
|
| - * 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);
|
| - }
|
| -
|
| - /**
|
| - * 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;
|
| - });
|
| - }
|
| -
|
| - 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);
|
| - }
|
| - result.putIfAbsent(key, _createList).add(value);
|
| - }
|
| -
|
| - 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++;
|
| - }
|
| - parsePair(start, equalsIndex, i);
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * 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');
|
| - }
|
| - 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();
|
| - }
|
| -
|
| - /**
|
| - * 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);
|
| - }
|
| - 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;
|
| - }
|
| - 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;
|
| - }
|
| - }
|
| - 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) {
|
| - 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);
|
| - }
|
| - }
|
| - }
|
| - 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;
|
| - }
|
| - } else {
|
| - bytes[index] = value >> 8;
|
| - bytes[index + 1] = value & 0xff;
|
| - index += 2;
|
| - }
|
| - }
|
| - 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,
|
| - bool spaceToPlus);
|
| -
|
| - /**
|
| - * Convert a byte (2 character hex sequence) in string [s] starting
|
| - * at position [pos] to its ordinal value
|
| - */
|
| - static int _hexCharPairToByte(String s, int pos) {
|
| - int byte = 0;
|
| - for (int i = 0; i < 2; i++) {
|
| - var charCode = s.codeUnitAt(pos + i);
|
| - if (0x30 <= charCode && charCode <= 0x39) {
|
| - byte = byte * 16 + charCode - 0x30;
|
| - } else {
|
| - // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66).
|
| - charCode |= 0x20;
|
| - if (0x61 <= charCode && charCode <= 0x66) {
|
| - byte = byte * 16 + charCode - 0x57;
|
| - } else {
|
| - throw new ArgumentError("Invalid URL encoding");
|
| - }
|
| - }
|
| - }
|
| - return byte;
|
| - }
|
| -
|
| - /**
|
| - * Uri-decode a percent-encoded string.
|
| - *
|
| - * It unescapes the string [text] and returns the unescaped string.
|
| - *
|
| - * This function is similar to the JavaScript-function `decodeURI`.
|
| - *
|
| - * If [plusToSpace] is `true`, plus characters will be converted to spaces.
|
| - *
|
| - * The decoder will create a byte-list of the percent-encoded parts, and then
|
| - * decode the byte-list using [encoding]. The default encodingis UTF-8.
|
| - */
|
| - static String _uriDecode(String text,
|
| - int start,
|
| - int end,
|
| - Encoding encoding,
|
| - bool plusToSpace) {
|
| - assert(0 <= start);
|
| - assert(start <= end);
|
| - assert(end <= text.length);
|
| - assert(encoding != null);
|
| - // First check whether there is any characters which need special handling.
|
| - bool simple = true;
|
| - for (int i = start; i < end; i++) {
|
| - var codeUnit = text.codeUnitAt(i);
|
| - if (codeUnit > 127 ||
|
| - codeUnit == _PERCENT ||
|
| - (plusToSpace && codeUnit == _PLUS)) {
|
| - simple = false;
|
| - break;
|
| - }
|
| - }
|
| - List<int> bytes;
|
| - if (simple) {
|
| - if (UTF8 == encoding || LATIN1 == encoding || ASCII == encoding) {
|
| - return text.substring(start, end);
|
| - } else {
|
| - bytes = text.substring(start, end).codeUnits;
|
| - }
|
| - } else {
|
| - bytes = new List();
|
| - for (int i = start; i < end; i++) {
|
| - var codeUnit = text.codeUnitAt(i);
|
| - if (codeUnit > 127) {
|
| - throw new ArgumentError("Illegal percent encoding in URI");
|
| - }
|
| - if (codeUnit == _PERCENT) {
|
| - if (i + 3 > text.length) {
|
| - throw new ArgumentError('Truncated URI');
|
| - }
|
| - bytes.add(_hexCharPairToByte(text, i + 1));
|
| - i += 2;
|
| - } else if (plusToSpace && codeUnit == _PLUS) {
|
| - bytes.add(_SPACE);
|
| - } else {
|
| - bytes.add(codeUnit);
|
| - }
|
| - }
|
| - }
|
| - return encoding.decode(bytes);
|
| - }
|
| -
|
| - static bool _isAlphabeticCharacter(int codeUnit) {
|
| - var lowerCase = codeUnit | 0x20;
|
| - return (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_Z);
|
| - }
|
| -
|
| - static bool _isUnreservedChar(int char) {
|
| - return char < 127 &&
|
| - ((_unreservedTable[char >> 4] & (1 << (char & 0x0f))) != 0);
|
| - }
|
| -
|
| - // Tables of char-codes organized as a bit vector of 128 bits where
|
| - // each bit indicate whether a character code on the 0-127 needs to
|
| - // be escaped or not.
|
| -
|
| - // The unreserved characters of RFC 3986.
|
| - static const _unreservedTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // -.
|
| - 0x6000, // 0x20 - 0x2f 0000000000000110
|
| - // 0123456789
|
| - 0x03ff, // 0x30 - 0x3f 1111111111000000
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // The unreserved characters of RFC 2396.
|
| - static const _unreserved2396Table = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! '()* -.
|
| - 0x6782, // 0x20 - 0x2f 0100000111100110
|
| - // 0123456789
|
| - 0x03ff, // 0x30 - 0x3f 1111111111000000
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Table of reserved characters specified by ECMAScript 5.
|
| - static const _encodeFullTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! #$ &'()*+,-./
|
| - 0xffda, // 0x20 - 0x2f 0101101111111111
|
| - // 0123456789:; = ?
|
| - 0xafff, // 0x30 - 0x3f 1111111111110101
|
| - // @ABCDEFGHIJKLMNO
|
| - 0xffff, // 0x40 - 0x4f 1111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in the scheme.
|
| - static const _schemeTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // + -.
|
| - 0x6800, // 0x20 - 0x2f 0000000000010110
|
| - // 0123456789
|
| - 0x03ff, // 0x30 - 0x3f 1111111111000000
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ
|
| - 0x07ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz
|
| - 0x07ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in scheme except for upper case letters.
|
| - static const _schemeLowerTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // + -.
|
| - 0x6800, // 0x20 - 0x2f 0000000000010110
|
| - // 0123456789
|
| - 0x03ff, // 0x30 - 0x3f 1111111111000000
|
| - //
|
| - 0x0000, // 0x40 - 0x4f 0111111111111111
|
| - //
|
| - 0x0000, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz
|
| - 0x07ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Sub delimiter characters combined with unreserved as of 3986.
|
| - // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
| - // / "*" / "+" / "," / ";" / "="
|
| - // RFC 3986 section 2.3.
|
| - // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
| - static const _subDelimitersTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $ &'()*+,-.
|
| - 0x7fd2, // 0x20 - 0x2f 0100101111111110
|
| - // 0123456789 ; =
|
| - 0x2bff, // 0x30 - 0x3f 1111111111010100
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // General delimiter characters, RFC 3986 section 2.2.
|
| - // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
| - //
|
| - static const _genDelimitersTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // # /
|
| - 0x8008, // 0x20 - 0x2f 0001000000000001
|
| - // : ?
|
| - 0x8400, // 0x30 - 0x3f 0000000000100001
|
| - // @
|
| - 0x0001, // 0x40 - 0x4f 1000000000000000
|
| - // [ ]
|
| - 0x2800, // 0x50 - 0x5f 0000000000010100
|
| - //
|
| - 0x0000, // 0x60 - 0x6f 0000000000000000
|
| - //
|
| - 0x0000]; // 0x70 - 0x7f 0000000000000000
|
| -
|
| - // Characters allowed in the userinfo as of RFC 3986.
|
| - // RFC 3986 Apendix A
|
| - // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
|
| - static const _userinfoTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $ &'()*+,-.
|
| - 0x7fd2, // 0x20 - 0x2f 0100101111111110
|
| - // 0123456789:; =
|
| - 0x2fff, // 0x30 - 0x3f 1111111111110100
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in the reg-name as of RFC 3986.
|
| - // RFC 3986 Apendix A
|
| - // reg-name = *( unreserved / pct-encoded / sub-delims )
|
| - static const _regNameTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $%&'()*+,-.
|
| - 0x7ff2, // 0x20 - 0x2f 0100111111111110
|
| - // 0123456789 ; =
|
| - 0x2bff, // 0x30 - 0x3f 1111111111010100
|
| - // ABCDEFGHIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 0111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in the path as of RFC 3986.
|
| - // RFC 3986 section 3.3.
|
| - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
| - static const _pathCharTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $ &'()*+,-.
|
| - 0x7fd2, // 0x20 - 0x2f 0100101111111110
|
| - // 0123456789:; =
|
| - 0x2fff, // 0x30 - 0x3f 1111111111110100
|
| - // @ABCDEFGHIJKLMNO
|
| - 0xffff, // 0x40 - 0x4f 1111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in the path as of RFC 3986.
|
| - // RFC 3986 section 3.3 *and* slash.
|
| - static const _pathCharOrSlashTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $ &'()*+,-./
|
| - 0xffd2, // 0x20 - 0x2f 0100101111111111
|
| - // 0123456789:; =
|
| - 0x2fff, // 0x30 - 0x3f 1111111111110100
|
| - // @ABCDEFGHIJKLMNO
|
| - 0xffff, // 0x40 - 0x4f 1111111111111111
|
| -
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| - // Characters allowed in the query as of RFC 3986.
|
| - // RFC 3986 section 3.4.
|
| - // query = *( pchar / "/" / "?" )
|
| - static const _queryCharTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 0000000000000000
|
| - 0x0000, // 0x10 - 0x1f 0000000000000000
|
| - // ! $ &'()*+,-./
|
| - 0xffd2, // 0x20 - 0x2f 0100101111111111
|
| - // 0123456789:; = ?
|
| - 0xafff, // 0x30 - 0x3f 1111111111110101
|
| - // @ABCDEFGHIJKLMNO
|
| - 0xffff, // 0x40 - 0x4f 1111111111111111
|
| - // PQRSTUVWXYZ _
|
| - 0x87ff, // 0x50 - 0x5f 1111111111100001
|
| - // abcdefghijklmno
|
| - 0xfffe, // 0x60 - 0x6f 0111111111111111
|
| - // pqrstuvwxyz ~
|
| - 0x47ff]; // 0x70 - 0x7f 1111111111100010
|
| -
|
| -}
|
| -
|
| -// --------------------------------------------------------------------
|
| -// Data URI
|
| -// --------------------------------------------------------------------
|
| -
|
| -/**
|
| - * A way to access the structure of a `data:` URI.
|
| - *
|
| - * Data URIs are non-hierarchical URIs that can contain any binary data.
|
| - * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397).
|
| - *
|
| - * This class allows parsing the URI text and extracting individual parts of the
|
| - * URI, as well as building the URI text from structured parts.
|
| - */
|
| -class UriData {
|
| - static const int _noScheme = -1;
|
| - /**
|
| - * Contains the text content of a `data:` URI, with or without a
|
| - * leading `data:`.
|
| - *
|
| - * If [_separatorIndices] starts with `4` (the index of the `:`), then
|
| - * there is a leading `data:`, otherwise [_separatorIndices] starts with
|
| - * `-1`.
|
| - */
|
| - final String _text;
|
| -
|
| - /**
|
| - * List of the separators (';', '=' and ',') in the text.
|
| - *
|
| - * Starts with the index of the `:` in `data:` of the mimeType.
|
| - * That is always either -1 or 4, depending on whether `_text` includes the
|
| - * `data:` scheme or not.
|
| - *
|
| - * The first speparator ends the mime type. We don't bother with finding
|
| - * the '/' inside the mime type.
|
| - *
|
| - * Each two separators after that marks a parameter key and value.
|
| - *
|
| - * If there is a single separator left, it ends the "base64" marker.
|
| - *
|
| - * So the following separators are found for a text:
|
| - *
|
| - * data:text/plain;foo=bar;base64,ARGLEBARGLE=
|
| - * ^ ^ ^ ^ ^
|
| - *
|
| - */
|
| - final List<int> _separatorIndices;
|
| -
|
| - /**
|
| - * Cache of the result returned by [uri].
|
| - */
|
| - Uri _uriCache;
|
| -
|
| - UriData._(this._text, this._separatorIndices, this._uriCache);
|
| -
|
| - /**
|
| - * Creates a `data:` URI containing the [content] string.
|
| - *
|
| - * Equivalent to `new Uri.dataFromString(...).data`, but may
|
| - * be more efficient if the [uri] itself isn't used.
|
| - */
|
| - factory UriData.fromString(String content,
|
| - {String mimeType,
|
| - Encoding encoding,
|
| - Map<String, String> parameters,
|
| - bool base64: false}) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - List<int> indices = [_noScheme];
|
| - String charsetName;
|
| - String encodingName;
|
| - if (parameters != null) charsetName = parameters["charset"];
|
| - if (encoding == null) {
|
| - if (charsetName != null) {
|
| - encoding = Encoding.getByName(charsetName);
|
| - }
|
| - } else if (charsetName == null) {
|
| - // Non-null only if parameters does not contain "charset".
|
| - encodingName = encoding.name;
|
| - }
|
| - encoding ??= ASCII;
|
| - _writeUri(mimeType, encodingName, parameters, buffer, indices);
|
| - indices.add(buffer.length);
|
| - if (base64) {
|
| - buffer.write(';base64,');
|
| - indices.add(buffer.length - 1);
|
| - buffer.write(encoding.fuse(BASE64).encode(content));
|
| - } else {
|
| - buffer.write(',');
|
| - _uriEncodeBytes(_uricTable, encoding.encode(content), buffer);
|
| - }
|
| - return new UriData._(buffer.toString(), indices, null);
|
| - }
|
| -
|
| - /**
|
| - * Creates a `data:` URI containing an encoding of [bytes].
|
| - *
|
| - * Equivalent to `new Uri.dataFromBytes(...).data`, but may
|
| - * be more efficient if the [uri] itself isn't used.
|
| - */
|
| - factory UriData.fromBytes(List<int> bytes,
|
| - {mimeType: "application/octet-stream",
|
| - Map<String, String> parameters,
|
| - percentEncoded: false}) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - List<int> indices = [_noScheme];
|
| - _writeUri(mimeType, null, parameters, buffer, indices);
|
| - indices.add(buffer.length);
|
| - if (percentEncoded) {
|
| - buffer.write(',');
|
| - _uriEncodeBytes(_uricTable, bytes, buffer);
|
| - } else {
|
| - buffer.write(';base64,');
|
| - indices.add(buffer.length - 1);
|
| - BASE64.encoder
|
| - .startChunkedConversion(
|
| - new StringConversionSink.fromStringSink(buffer))
|
| - .addSlice(bytes, 0, bytes.length, true);
|
| - }
|
| -
|
| - return new UriData._(buffer.toString(), indices, null);
|
| - }
|
| -
|
| - /**
|
| - * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme].
|
| - *
|
| - * The [uri] must have scheme `data` and no authority or fragment,
|
| - * and the path (concatenated with the query, if there is one) must be valid
|
| - * as data URI content with the same rules as [parse].
|
| - */
|
| - factory UriData.fromUri(Uri uri) {
|
| - if (uri.scheme != "data") {
|
| - throw new ArgumentError.value(uri, "uri",
|
| - "Scheme must be 'data'");
|
| - }
|
| - if (uri.hasAuthority) {
|
| - throw new ArgumentError.value(uri, "uri",
|
| - "Data uri must not have authority");
|
| - }
|
| - if (uri.hasFragment) {
|
| - throw new ArgumentError.value(uri, "uri",
|
| - "Data uri must not have a fragment part");
|
| - }
|
| - if (!uri.hasQuery) {
|
| - return _parse(uri.path, 0, uri);
|
| - }
|
| - // Includes path and query (and leading "data:").
|
| - return _parse("$uri", 5, uri);
|
| - }
|
| -
|
| - /**
|
| - * Writes the initial part of a `data:` uri, from after the "data:"
|
| - * until just before the ',' before the data, or before a `;base64,`
|
| - * marker.
|
| - *
|
| - * Of an [indices] list is passed, separator indices are stored in that
|
| - * list.
|
| - */
|
| - static void _writeUri(String mimeType,
|
| - String charsetName,
|
| - Map<String, String> parameters,
|
| - StringBuffer buffer, List indices) {
|
| - if (mimeType == null || mimeType == "text/plain") {
|
| - mimeType = "";
|
| - }
|
| - if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
|
| - buffer.write(mimeType); // Common cases need no escaping.
|
| - } else {
|
| - int slashIndex = _validateMimeType(mimeType);
|
| - if (slashIndex < 0) {
|
| - throw new ArgumentError.value(mimeType, "mimeType",
|
| - "Invalid MIME type");
|
| - }
|
| - buffer.write(Uri._uriEncode(_tokenCharTable,
|
| - mimeType.substring(0, slashIndex),
|
| - UTF8, false));
|
| - buffer.write("/");
|
| - buffer.write(Uri._uriEncode(_tokenCharTable,
|
| - mimeType.substring(slashIndex + 1),
|
| - UTF8, false));
|
| - }
|
| - if (charsetName != null) {
|
| - if (indices != null) {
|
| - indices..add(buffer.length)
|
| - ..add(buffer.length + 8);
|
| - }
|
| - buffer.write(";charset=");
|
| - buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
|
| - }
|
| - parameters?.forEach((var key, var value) {
|
| - if (key.isEmpty) {
|
| - throw new ArgumentError.value("", "Parameter names must not be empty");
|
| - }
|
| - if (value.isEmpty) {
|
| - throw new ArgumentError.value("", "Parameter values must not be empty",
|
| - 'parameters["$key"]');
|
| - }
|
| - 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));
|
| - if (indices != null) indices.add(buffer.length);
|
| - buffer.write('=');
|
| - buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Checks mimeType is valid-ish (`token '/' token`).
|
| - *
|
| - * Returns the index of the slash, or -1 if the mime type is not
|
| - * considered valid.
|
| - *
|
| - * Currently only looks for slashes, all other characters will be
|
| - * percent-encoded as UTF-8 if necessary.
|
| - */
|
| - static int _validateMimeType(String mimeType) {
|
| - int slashIndex = -1;
|
| - for (int i = 0; i < mimeType.length; i++) {
|
| - var char = mimeType.codeUnitAt(i);
|
| - if (char != Uri._SLASH) continue;
|
| - if (slashIndex < 0) {
|
| - slashIndex = i;
|
| - continue;
|
| - }
|
| - return -1;
|
| - }
|
| - return slashIndex;
|
| - }
|
| -
|
| - /**
|
| - * Parses a string as a `data` URI.
|
| - *
|
| - * The string must have the format:
|
| - *
|
| - * ```
|
| - * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' data
|
| - * ````
|
| - *
|
| - * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
|
| - * 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
|
| - * to be converted to the corresponding string.
|
| - *
|
| - * Parsing doesn't check the validity of any part, it just checks that the
|
| - * input has the correct structure with the correct sequence of `/`, `;`, `=`
|
| - * and `,` delimiters.
|
| - *
|
| - * Accessing the individual parts may fail later if they turn out to have
|
| - * content that can't be decoded sucessfully as a string.
|
| - */
|
| - static UriData parse(String uri) {
|
| - if (!uri.startsWith("data:")) {
|
| - throw new FormatException("Does not start with 'data:'", uri, 0);
|
| - }
|
| - return _parse(uri, 5, null);
|
| - }
|
| -
|
| - /**
|
| - * The [Uri] that this `UriData` is giving access to.
|
| - *
|
| - * Returns a `Uri` with scheme `data` and the remainder of the data URI
|
| - * as path.
|
| - */
|
| - Uri get uri {
|
| - if (_uriCache != null) return _uriCache;
|
| - String path = _text;
|
| - String query = null;
|
| - int colonIndex = _separatorIndices[0];
|
| - int queryIndex = _text.indexOf('?', colonIndex + 1);
|
| - int end = null;
|
| - if (queryIndex >= 0) {
|
| - query = _text.substring(queryIndex + 1);
|
| - end = queryIndex;
|
| - }
|
| - path = _text.substring(colonIndex + 1, end);
|
| - // TODO(lrn): This is probably too simple. We should ensure URI
|
| - // normalization before passing in the raw strings, maybe using
|
| - // Uri._makePath, Uri._makeQuery.
|
| - _uriCache = new Uri._internal("data", "", null, null, path, query, null);
|
| - return _uriCache;
|
| - }
|
| -
|
| - /**
|
| - * The MIME type of the data URI.
|
| - *
|
| - * A data URI consists of a "media type" followed by data.
|
| - * The media type starts with a MIME type and can be followed by
|
| - * extra parameters.
|
| - *
|
| - * Example:
|
| - *
|
| - * data:text/plain;charset=utf-8,Hello%20World!
|
| - *
|
| - * This data URI has the media type `text/plain;charset=utf-8`, which is the
|
| - * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
|
| - * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
|
| - *
|
| - * If the first part of the data URI is empty, it defaults to `text/plain`.
|
| - */
|
| - String get mimeType {
|
| - int start = _separatorIndices[0] + 1;
|
| - int end = _separatorIndices[1];
|
| - if (start == end) return "text/plain";
|
| - return Uri._uriDecode(_text, start, end, UTF8, false);
|
| - }
|
| -
|
| - /**
|
| - * The charset parameter of the media type.
|
| - *
|
| - * If the parameters of the media type contains a `charset` parameter
|
| - * then this returns its value, otherwise it returns `US-ASCII`,
|
| - * which is the default charset for data URIs.
|
| - */
|
| - String get charset {
|
| - int parameterStart = 1;
|
| - int parameterEnd = _separatorIndices.length - 1; // The ',' before data.
|
| - if (isBase64) {
|
| - // There is a ";base64" separator, so subtract one for that as well.
|
| - parameterEnd -= 1;
|
| - }
|
| - for (int i = parameterStart; i < parameterEnd; i += 2) {
|
| - 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 "US-ASCII";
|
| - }
|
| -
|
| - /**
|
| - * Whether the data is Base64 encoded or not.
|
| - */
|
| - bool get isBase64 => _separatorIndices.length.isOdd;
|
| -
|
| - /**
|
| - * The content part of the data URI, as its actual representation.
|
| - *
|
| - * This string may contain percent escapes.
|
| - */
|
| - String get contentText => _text.substring(_separatorIndices.last + 1);
|
| -
|
| - /**
|
| - * The content part of the data URI as bytes.
|
| - *
|
| - * If the data is Base64 encoded, it will be decoded to bytes.
|
| - *
|
| - * If the data is not Base64 encoded, it will be decoded by unescaping
|
| - * percent-escaped characters and returning byte values of each unescaped
|
| - * character. The bytes will not be, e.g., UTF-8 decoded.
|
| - */
|
| - List<int> contentAsBytes() {
|
| - String text = _text;
|
| - int start = _separatorIndices.last + 1;
|
| - if (isBase64) {
|
| - return BASE64.decoder.convert(text, start);
|
| - }
|
| -
|
| - // Not base64, do percent-decoding and return the remaining bytes.
|
| - // Compute result size.
|
| - const int percent = 0x25;
|
| - int length = text.length - start;
|
| - for (int i = start; i < text.length; i++) {
|
| - var codeUnit = text.codeUnitAt(i);
|
| - if (codeUnit == percent) {
|
| - i += 2;
|
| - length -= 2;
|
| - }
|
| - }
|
| - // Fill result array.
|
| - Uint8List result = new Uint8List(length);
|
| - if (length == text.length) {
|
| - result.setRange(0, length, text.codeUnits, start);
|
| - return result;
|
| - }
|
| - int index = 0;
|
| - for (int i = start; i < text.length; i++) {
|
| - var codeUnit = text.codeUnitAt(i);
|
| - if (codeUnit != percent) {
|
| - 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));
|
| - if (digit1 >= 0 && digit2 >= 0) {
|
| - int byte = digit1 * 16 + digit2;
|
| - result[index++] = byte;
|
| - i += 2;
|
| - continue;
|
| - }
|
| - }
|
| - throw new FormatException("Invalid percent escape", text, i);
|
| - }
|
| - }
|
| - assert(index == result.length);
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Returns a string created from the content of the data URI.
|
| - *
|
| - * 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 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.
|
| - *
|
| - * If the content is not Base64 encoded, it will first have percent-escapes
|
| - * converted to bytes and then the character codes and byte values are
|
| - * decoded using [encoding].
|
| - */
|
| - String contentAsString({Encoding encoding}) {
|
| - if (encoding == null) {
|
| - var charset = this.charset; // Returns "US-ASCII" if not present.
|
| - encoding = Encoding.getByName(charset);
|
| - if (encoding == null) {
|
| - throw new UnsupportedError("Unknown charset: $charset");
|
| - }
|
| - }
|
| - String text = _text;
|
| - int start = _separatorIndices.last + 1;
|
| - if (isBase64) {
|
| - var converter = BASE64.decoder.fuse(encoding.decoder);
|
| - return converter.convert(text.substring(start));
|
| - }
|
| - return Uri._uriDecode(text, start, text.length, encoding, false);
|
| - }
|
| -
|
| - /**
|
| - * A map representing the parameters of the media type.
|
| - *
|
| - * A data URI may contain parameters between the MIME type and the
|
| - * data. This converts these parameters to a map from parameter name
|
| - * to parameter value.
|
| - * The map only contains parameters that actually occur in the URI.
|
| - * The `charset` parameter has a default value even if it doesn't occur
|
| - * in the URI, which is reflected by the [charset] getter. This means that
|
| - * [charset] may return a value even if `parameters["charset"]` is `null`.
|
| - *
|
| - * If the values contain non-ASCII values or percent escapes, they default
|
| - * to being decoded as UTF-8.
|
| - */
|
| - Map<String, String> get parameters {
|
| - var result = <String, String>{};
|
| - for (int i = 3; i < _separatorIndices.length; i += 2) {
|
| - 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);
|
| - result[key] = value;
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - static UriData _parse(String text, int start, Uri sourceUri) {
|
| - assert(start == 0 || start == 5);
|
| - assert((start == 5) == text.startsWith("data:"));
|
| -
|
| - /// Character codes.
|
| - const int comma = 0x2c;
|
| - const int slash = 0x2f;
|
| - const int semicolon = 0x3b;
|
| - const int equals = 0x3d;
|
| - List<int> indices = [start - 1];
|
| - int slashIndex = -1;
|
| - var char;
|
| - int i = start;
|
| - for (; i < text.length; i++) {
|
| - char = text.codeUnitAt(i);
|
| - if (char == comma || char == semicolon) break;
|
| - if (char == slash) {
|
| - if (slashIndex < 0) {
|
| - slashIndex = i;
|
| - continue;
|
| - }
|
| - throw new FormatException("Invalid MIME type", text, i);
|
| - }
|
| - }
|
| - if (slashIndex < 0 && i > start) {
|
| - // An empty MIME type is allowed, but if non-empty it must contain
|
| - // exactly one slash.
|
| - throw new FormatException("Invalid MIME type", text, i);
|
| - }
|
| - while (char != comma) {
|
| - // Parse parameters and/or "base64".
|
| - indices.add(i);
|
| - i++;
|
| - int equalsIndex = -1;
|
| - for (; i < text.length; i++) {
|
| - char = text.codeUnitAt(i);
|
| - if (char == equals) {
|
| - if (equalsIndex < 0) equalsIndex = i;
|
| - } else if (char == semicolon || char == comma) {
|
| - break;
|
| - }
|
| - }
|
| - if (equalsIndex >= 0) {
|
| - indices.add(equalsIndex);
|
| - } else {
|
| - // Have to be final "base64".
|
| - var lastSeparator = indices.last;
|
| - if (char != comma ||
|
| - i != lastSeparator + 7 /* "base64,".length */ ||
|
| - !text.startsWith("base64", lastSeparator + 1)) {
|
| - throw new FormatException("Expecting '='", text, i);
|
| - }
|
| - break;
|
| - }
|
| - }
|
| - indices.add(i);
|
| - return new UriData._(text, indices, sourceUri);
|
| - }
|
| -
|
| - /**
|
| - * Like [Uri._uriEncode] but takes the input as bytes, not a string.
|
| - *
|
| - * Encodes into [buffer] instead of creating its own buffer.
|
| - */
|
| - static void _uriEncodeBytes(List<int> canonicalTable,
|
| - List<int> bytes,
|
| - StringSink buffer) {
|
| - // Encode the string into bytes then generate an ASCII only string
|
| - // by percent encoding selected bytes.
|
| - int byteOr = 0;
|
| - for (int i = 0; i < bytes.length; i++) {
|
| - int byte = bytes[i];
|
| - byteOr |= byte;
|
| - if (byte < 128 &&
|
| - ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
|
| - buffer.writeCharCode(byte);
|
| - } else {
|
| - buffer.writeCharCode(Uri._PERCENT);
|
| - buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
|
| - buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
|
| - }
|
| - }
|
| - if ((byteOr & ~0xFF) != 0) {
|
| - for (int i = 0; i < bytes.length; i++) {
|
| - var byte = bytes[i];
|
| - if (byte < 0 || byte > 255) {
|
| - throw new ArgumentError.value(byte, "non-byte value");
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - String toString() =>
|
| - (_separatorIndices[0] == _noScheme) ? "data:$_text" : _text;
|
| -
|
| - // Table of the `token` characters of RFC 2045 in a URI.
|
| - //
|
| - // A token is any US-ASCII character except SPACE, control characters and
|
| - // `tspecial` characters. The `tspecial` category is:
|
| - // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='.
|
| - //
|
| - // In a data URI, we also need to escape '%' and '#' characters.
|
| - static const _tokenCharTable = const [
|
| - // LSB MSB
|
| - // | |
|
| - 0x0000, // 0x00 - 0x0f 00000000 00000000
|
| - 0x0000, // 0x10 - 0x1f 00000000 00000000
|
| - // ! $ &' *+ -.
|
| - 0x6cd2, // 0x20 - 0x2f 01001011 00110110
|
| - // 01234567 89
|
| - 0x03ff, // 0x30 - 0x3f 11111111 11000000
|
| - // ABCDEFG HIJKLMNO
|
| - 0xfffe, // 0x40 - 0x4f 01111111 11111111
|
| - // PQRSTUVW XYZ ^_
|
| - 0xc7ff, // 0x50 - 0x5f 11111111 11100011
|
| - // `abcdefg hijklmno
|
| - 0xffff, // 0x60 - 0x6f 11111111 11111111
|
| - // pqrstuvw xyz{|}~
|
| - 0x7fff]; // 0x70 - 0x7f 11111111 11111110
|
| -
|
| - // All non-escape RFC-2396 uric characters.
|
| - //
|
| - // uric = reserved | unreserved | escaped
|
| - // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
|
| - // unreserved = alphanum | mark
|
| - // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
|
| - //
|
| - // This is the same characters as in a URI query (which is URI pchar plus '?')
|
| - static const _uricTable = Uri._queryCharTable;
|
| -}
|
|
|