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