Chromium Code Reviews| Index: sdk/lib/core/uri.dart |
| diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart |
| index 5718ca80b83b870dd578011592707b4c7d99cdc4..a89f8d4b445abc4870142572e78b459e8e293a91 100644 |
| --- a/sdk/lib/core/uri.dart |
| +++ b/sdk/lib/core/uri.dart |
| @@ -73,6 +73,11 @@ class Uri { |
| List<String> _pathSegments; |
| /** |
| + * Cache of the full normalized text representation of the URI. |
| + */ |
| + String _text; |
| + |
| + /** |
| * Cache the computed return value of [queryParameters]. |
| */ |
| Map<String, String> _queryParameters; |
| @@ -158,8 +163,8 @@ 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, |
| @@ -394,199 +399,192 @@ class Uri { |
| // query = *( pchar / "/" / "?" ) |
| // |
| // fragment = *( pchar / "/" / "?" ) |
| - const int EOI = -1; |
| + end ??= uri.length; |
| - String scheme = ""; |
| - String userinfo = ""; |
| - String host = null; |
| - int port = null; |
| - String path = null; |
| - String query = null; |
| - String fragment = null; |
| - if (end == null) end = uri.length; |
| - |
| - int index = start; |
| - int pathStart = start; |
| - // End of input-marker. |
| - int char = EOI; |
| - |
| - void parseAuth() { |
| - if (index == end) { |
| - char = EOI; |
| - return; |
| - } |
| - int authStart = index; |
| - int lastColon = -1; |
| - int lastAt = -1; |
| - char = uri.codeUnitAt(index); |
| - while (index < end) { |
| - char = uri.codeUnitAt(index); |
| - if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) { |
| - break; |
| - } |
| - if (char == _AT_SIGN) { |
| - lastAt = index; |
| - lastColon = -1; |
| - } else if (char == _COLON) { |
| - lastColon = index; |
| - } else if (char == _LEFT_BRACKET) { |
| - lastColon = -1; |
| - int endBracket = uri.indexOf(']', index + 1); |
| - if (endBracket == -1) { |
| - index = end; |
| - char = EOI; |
| - break; |
| - } else { |
| - index = endBracket; |
| - } |
| - } |
| - index++; |
| - char = EOI; |
| - } |
| - int hostStart = authStart; |
| - int hostEnd = index; |
| - if (lastAt >= 0) { |
| - userinfo = _makeUserInfo(uri, authStart, lastAt); |
| - hostStart = lastAt + 1; |
| - } |
| - if (lastColon >= 0) { |
| - int portNumber; |
| - if (lastColon + 1 < index) { |
| - portNumber = 0; |
| - for (int i = lastColon + 1; i < index; i++) { |
| - int digit = uri.codeUnitAt(i); |
| - if (_ZERO > digit || _NINE < digit) { |
| - _fail(uri, i, "Invalid port number"); |
| - } |
| - portNumber = portNumber * 10 + (digit - _ZERO); |
| - } |
| - } |
| - port = _makePort(portNumber, scheme); |
| - hostEnd = lastColon; |
| - } |
| - host = _makeHost(uri, hostStart, hostEnd, true); |
| - if (index < end) { |
| - char = uri.codeUnitAt(index); |
| + // Special case data:URIs. Ignore case when testing. |
| + if (end >= start + 5 && |
| + uri.codeUnitAt(start + 4) == _COLON && |
| + uri.codeUnitAt(start) | 0x20 == 0x64 /*d*/ && |
| + uri.codeUnitAt(start + 1) | 0x20 == 0x61 /*a*/ && |
| + uri.codeUnitAt(start + 2) | 0x20 == 0x74 /*t*/ && |
| + uri.codeUnitAt(start + 3) | 0x20 == 0x61 /*a*/) { |
| + // Data-URI. |
| + if (uri.startsWith("data", start)) { |
| + // The case is right. |
| + if (start > 0 || end < uri.length) uri = uri.substring(start, end); |
| + return UriData._parse(uri, 5, null).uri; |
| } |
| + return Uriata._parse(uri.substring(start + 5, end), 0, null).uri; |
|
floitsch
2016/06/28 00:28:03
UriData
I'm guessing you didn't run the tests?
Lasse Reichstein Nielsen
2016/06/28 13:00:51
I did. Turns out we never tested a data URI starti
|
| } |
| - // 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; |
| + var indices = _scanUri(uri, start, end); |
|
floitsch
2016/06/28 00:28:03
Provide example of what the indices could look lik
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Done.
|
| - // Current state. |
| - // Initialized to the default value that is used when exiting the |
| - // scheme loop by reaching the end of input. |
| - // All other breaks set their own state. |
| - int state = NOT_IN_PATH; |
| - int i = index; // Temporary alias for index to avoid bug 19550 in dart2js. |
| - while (i < end) { |
| - char = uri.codeUnitAt(i); |
| - if (char == _QUESTION || char == _NUMBER_SIGN) { |
| - state = NOT_IN_PATH; |
| - break; |
| - } |
| - if (char == _SLASH) { |
| - state = (i == start) ? ALLOW_AUTH : IN_PATH; |
| - break; |
| + int schemeEnd = indices[_schemeEndIndex]; // >0 if has scheme |
|
floitsch
2016/06/28 00:28:04
Finish with ".".
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Just removed, it's documented in the _scanUri docs
|
| + int hostStart = indices[_hostStartIndex] + 1; // >0 if has authority. |
| + 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 indices that weren't set. |
| + // If fragment but no query, set query to start at fragment. |
| + if (fragmentStart < queryStart) queryStart = fragmentStart; |
|
floitsch
2016/06/28 00:28:03
It's not clear why this wouldn't be done by the _s
Lasse Reichstein Nielsen
2016/06/28 13:00:52
It absolutely could. More to the point, the _scanU
|
| + // If scheme but no authority, the pathStart isn't set. |
| + if (schemeEnd >= start && hostStart <= start) pathStart = schemeEnd + 1; |
| + // If scheme or authority but pathStart isn't set. |
| + if (pathStart == start && (schemeEnd >= start || hostStart > start)) { |
| + pathStart = queryStart; |
| + } |
| + // If authority and no port. |
| + // (including when user-info contains : and portStart >= 0). |
| + if (portStart < hostStart) portStart = pathStart; |
| + |
| + 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. |
| + isSimple = false; |
| } |
|
floitsch
2016/06/28 00:28:03
Is it necessary to enter the 'else' below?
if not
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Done.
|
| - if (char == _COLON) { |
| - 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; |
| - } |
| - pathStart = i; |
| - if (i == end) { |
| - char = EOI; |
| - state = NOT_IN_PATH; |
| - } else { |
| - char = uri.codeUnitAt(i); |
| - if (char == _QUESTION || char == _NUMBER_SIGN) { |
| - state = NOT_IN_PATH; |
| - } else if (char == _SLASH) { |
| - state = ALLOW_AUTH; |
| - } else { |
| - state = IN_PATH; |
| + if (portStart > start && portStart + 1 == pathStart) { |
| + // If the port is empty, it should be omitted. |
| + // Path case, don't bother correcting it. |
| + isSimple = false; |
| + } else if (hostStart == schemeEnd + 4) { |
| + // If the userInfo is empty, it should be omitted. |
| + // (4 is length of "://@"). |
| + // Pathological case, don't bother correcting it. |
| + 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 <= 0) { |
| + // File URIs should have an authority. |
| + // Paths after an authority should be absolute. |
| + String insert = "//"; |
| + int delta = 2; |
| + if (!uri.startsWith("/", pathStart)) { |
| + insert = "///"; |
| + delta = 3; |
| + } |
| + uri = "${uri.substring(start, schemeEnd + 1)}$insert" |
| + "${uri.substring(pathStart, end)}"; |
| + schemeEnd -= start; |
| + pathStart += 2 - start; |
| + hostStart = schemeEnd + 3; |
| + portStart = pathStart; |
| + queryStart += delta; |
| + fragmentStart += delta; |
| + start = 0; |
| + end = uri.length; |
| + } else if (pathStart == queryStart) { |
| + uri = "${uri.substring(start, pathStart)}/" |
| + "${uri.substring(pathStart, end)}"; |
| + queryStart += 1; |
| + fragmentStart += 1; |
| + } |
| + } else if (uri.startsWith("http", start)) { |
| + scheme = "http"; |
| + // Http URIs should not have an explicit port of 80 |
|
floitsch
2016/06/28 00:28:03
Finish with ".".
Lasse Reichstein Nielsen
2016/06/28 13:00:51
Done.
|
| + if (portStart > start && portStart + 3 == pathStart && |
| + uri.startsWith("80", portStart + 1)) { |
| + 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)) { |
| + 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; |
| } |
| } |
| - break; |
| } |
| - i++; |
| - char = EOI; |
| } |
| - index = i; // Remove alias when bug is fixed. |
| - if (state == ALLOW_AUTH) { |
| - assert(char == _SLASH); |
| - // Have seen one slash either at start or right after scheme. |
| - // If two slashes, it's an authority, otherwise it's just the path. |
| - index++; |
| - if (index == end) { |
| - char = EOI; |
| - state = NOT_IN_PATH; |
| - } else { |
| - char = uri.codeUnitAt(index); |
| - if (char == _SLASH) { |
| - index++; |
| - parseAuth(); |
| - pathStart = index; |
| - } |
| - if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) { |
| - state = NOT_IN_PATH; |
| - } else { |
| - state = IN_PATH; |
| + if (isSimple) { |
| + if (start > 0 || end < uri.length) { |
| + uri = uri.substring(start, end); |
| + if (schemeEnd >= 0) schemeEnd -= start; |
| + if (hostStart > 0) { |
| + hostStart -= start; |
| + portStart -= start; |
| } |
| + pathStart -= start; |
| + queryStart -= start; |
| + fragmentStart -= start; |
| } |
| + return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart, |
| + queryStart, fragmentStart, 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; |
| + if (scheme == null) { |
| + scheme = ""; |
| + if (schemeEnd > start) { |
| + scheme = _makeScheme(uri, start, schemeEnd); |
| + } else if (schemeEnd == start) { |
| + _fail(uri, start, "Invalid empty scheme"); |
| } |
| - state = NOT_IN_PATH; |
| } |
| - |
| - assert(state == NOT_IN_PATH); |
| - bool hasAuthority = (host != null); |
| - path = _makePath(uri, pathStart, index, null, scheme, hasAuthority); |
| - |
| - if (char == _QUESTION) { |
| - int numberSignIndex = -1; |
| - for (int i = index + 1; i < end; i++) { |
| - if (uri.codeUnitAt(i) == _NUMBER_SIGN) { |
| - numberSignIndex = i; |
| - break; |
| - } |
| + String userInfo = ""; |
| + String host; |
| + int port; |
| + if (hostStart > start) { |
| + int userInfoStart = schemeEnd + 3; |
| + if (userInfoStart < hostStart) { |
| + userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1); |
| } |
| - if (numberSignIndex < 0) { |
| - query = _makeQuery(uri, index + 1, end, null); |
| - } else { |
| - query = _makeQuery(uri, index + 1, numberSignIndex, null); |
| - fragment = _makeFragment(uri, numberSignIndex + 1, end); |
| + 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); |
| } |
| - } else if (char == _NUMBER_SIGN) { |
| - fragment = _makeFragment(uri, index + 1, end); |
| + } |
| + 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, |
| + userInfo, |
| host, |
| port, |
| path, |
| @@ -1333,6 +1331,17 @@ class 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; |
| } |
| @@ -1932,6 +1941,8 @@ class Uri { |
| * 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}) { |
| if (scheme != "" && scheme != "file") { |
| throw new UnsupportedError( |
| @@ -1946,25 +1957,27 @@ class Uri { |
| "Cannot extract a file path from a URI with a fragment component"); |
| } |
| if (windows == null) windows = _isWindows; |
| - return windows ? _toWindowsFilePath() : _toFilePath(); |
| + return windows ? _toWindowsFilePath(this) : _toFilePath(); |
| } |
| String _toFilePath() { |
| - if (host != "") { |
| + if (hasAuthority && host != "") { |
| throw new UnsupportedError( |
| "Cannot extract a non-Windows file path from a file URI " |
| "with an authority"); |
| } |
| + // Use path segments to have any escapes unescaped. |
| + var pathSegments = this.pathSegments; |
| _checkNonWindowsPathReservedCharacters(pathSegments, false); |
| var result = new StringBuffer(); |
| - if (_isPathAbsolute) result.write("/"); |
| + if (hasAbsolutePath) result.write("/"); |
| result.writeAll(pathSegments, "/"); |
| return result.toString(); |
| } |
| - String _toWindowsFilePath() { |
| + static String _toWindowsFilePath(Uri uri) { |
| bool hasDriveLetter = false; |
| - var segments = pathSegments; |
| + var segments = uri.pathSegments; |
| if (segments.length > 0 && |
| segments[0].length == 2 && |
| segments[0].codeUnitAt(1) == _COLON) { |
| @@ -1972,23 +1985,25 @@ class Uri { |
| _checkWindowsPathReservedCharacters(segments, false, 1); |
| hasDriveLetter = true; |
| } else { |
| - _checkWindowsPathReservedCharacters(segments, false); |
| + _checkWindowsPathReservedCharacters(segments, false, 0); |
| } |
| var result = new StringBuffer(); |
| - if (_isPathAbsolute && !hasDriveLetter) result.write("\\"); |
| - if (host != "") { |
| - result.write("\\"); |
| - result.write(host); |
| - result.write("\\"); |
| + 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"\"); |
| + } |
| } |
| - result.writeAll(segments, "\\"); |
| - if (hasDriveLetter && segments.length == 1) result.write("\\"); |
| + result.writeAll(segments, r"\"); |
| + if (hasDriveLetter && segments.length == 1) result.write(r"\"); |
| return result.toString(); |
| } |
| bool get _isPathAbsolute { |
| - if (path == null || path.isEmpty) return false; |
| - return path.startsWith('/'); |
| + return _path != null && _path.startsWith('/'); |
| } |
| void _writeAuthority(StringSink ss) { |
| @@ -2014,8 +2029,9 @@ class Uri { |
| UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null; |
| String toString() { |
| + if (_text != null) return _text; |
| StringBuffer sb = new StringBuffer(); |
| - _addIfNonEmpty(sb, scheme, scheme, ':'); |
| + 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". |
| @@ -2023,9 +2039,10 @@ class Uri { |
| _writeAuthority(sb); |
| } |
| sb.write(path); |
| - if (_query != null) { sb..write("?")..write(_query); } |
| - if (_fragment != null) { sb..write("#")..write(_fragment); } |
| - return sb.toString(); |
| + if (_query != null) sb..write("?")..write(_query); |
| + if (_fragment != null) sb..write("#")..write(_fragment); |
| + _text = sb.toString(); |
| + return _text; |
| } |
| bool operator==(other) { |
| @@ -2044,20 +2061,7 @@ class Uri { |
| } |
| 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); |
| - } |
| + return (_text ?? toString()).hashCode; |
| } |
| /** |
| @@ -3359,3 +3363,713 @@ class UriData { |
| // 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. |
| + |
| +const int _nopIndex = 0; |
| +const int _schemeEndIndex = 1; |
| +const int _hostStartIndex = 2; |
| +const int _portStartIndex = 3; |
| +const int _pathStartIndex = 4; |
| +const int _queryStartIndex = 5; |
| +const int _fragmentStartIndex = 6; |
| +const int _notSimpleIndex = 7; |
| + |
| +// Initial state for scanner. |
| +const int _start = 00; |
| + |
| +final _scannerTables = _createTables(); |
| + |
| +// ---------------------------------------------------------------------- |
| +// Code to create the URI scanner table. |
| + |
| +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; |
| + |
| +const int _scheme0 = 20; |
| +const int _scheme = 21; |
| + |
| +const int _stateCount = 22; |
| +const int _nonSimpleEndStates = 14; |
|
floitsch
2016/06/28 00:28:04
This needs a comment and should be separated from
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Commented and moved. Most of the states have been
|
| + |
| +const int _nop = _nopIndex << 5; |
| +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; |
| + |
| + |
| +void _setChars(Uint8List target, String chars, int value) { |
|
floitsch
2016/06/28 00:28:03
Needs dartdocs.
Lasse Reichstein Nielsen
2016/06/28 13:00:51
Done.
|
| + for (int i = 0; i < chars.length; i++) { |
| + var char = chars.codeUnitAt(i); |
| + target[char ^ 0x60] = value; |
|
floitsch
2016/06/28 00:28:04
Needs explanation what the ^ 0x60 does.
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Now described on the _scannerTables declaration.
|
| + } |
| +} |
| + |
| +void _setRange(Uint8List target, String range, int value) { |
|
floitsch
2016/06/28 00:28:03
Needs dartdocs.
Lasse Reichstein Nielsen
2016/06/28 13:00:51
Done.
|
| + for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) { |
| + target[i ^ 0x60] = value; |
| + } |
| +} |
| + |
| +List<Uint8List> _createTables() { |
|
floitsch
2016/06/28 00:28:04
Needs documentation.
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Done.
|
| + // TODO(lrn): Use a precomputed table. |
| + var tables = new List<Uint8List>.generate(_stateCount, |
| + (_) => new Uint8List(96)); |
| + |
| + const unreserved = |
| + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~" ; |
| + const subDelims = r"!$&'()*+,;="; |
| + const pchar = "$unreserved$subDelims"; |
|
floitsch
2016/06/28 00:28:03
what does the "p" stand for?
Lasse Reichstein Nielsen
2016/06/28 13:00:52
This refers to the "pchar" character group in RFC
|
| + |
| + build(index, defaultValue) => |
| + tables[index]..fillRange(0, 96, defaultValue); |
| + |
| + var b; |
| + // Validate as path, if it is a scheme, we handle it later. |
| + b = build(_start, _schemeOrPath | _notSimple); |
| + _setChars(b, pchar, _schemeOrPath); |
| + _setChars(b, ".", _schemeOrPathDot); |
| + _setChars(b, "%", _schemeOrPath | _notSimple); |
| + _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, "%", _schemeOrPath | _notSimple); |
| + _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); |
| + _setChars(b, "/", _authOrPathSlash); |
| + _setChars(b, ".", _pathSegDot); |
| + _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); |
| + _setChars(b, "0", _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, ".", _path); |
| + _setChars(b, "/", _relPathSeg); |
| + _setChars(b, "?", _query | _queryStart); |
| + _setChars(b, "#", _fragment | _fragmentStart); |
| + |
| + 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, ".", _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 scheme names. |
| + // Only accepts lower-case letters. |
| + 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. |
| + |
| +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 0x60 to move range 0x20-0x7f into 0x00-0x5f |
| + int char = uri.codeUnitAt(i) ^ 0x60; |
| + // use 0x1f (nee 0x7f) to represent all unhandled characters. |
|
floitsch
2016/06/28 00:28:03
Use
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Done.
|
| + if (char > 0x5f) char = 0x1f; // TODO: check if negating test is better. |
|
floitsch
2016/06/28 00:28:03
TODO(ldap)
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Or check it. It isn't faster.
|
| + int transition = table[char]; |
| + state = transition & 0x1f; |
| + indices[transition >> 5] = i; |
| + } |
| + return state; |
| +} |
| + |
| +List<int> _scanUri(String uri, int start, int end) { |
| + var indices = new List<int>.filled(8, start - 1); |
| + indices[_portStartIndex] = start; // equals effective pathStart if no auth. |
|
floitsch
2016/06/28 00:28:04
"Equals"
Lasse Reichstein Nielsen
2016/06/28 13:00:51
Done.
|
| + indices[_pathStartIndex] = start; |
| + indices[_queryStartIndex] = end; |
| + indices[_fragmentStartIndex] = end; |
| + var state = _scan(uri, start, end, _start, indices); |
| + // Some states that should be non-simple, but the URI ended early. |
| + 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, _scheme0, indices); |
| + if (state == _scheme0) { |
| + // Empty scheme. |
| + indices[_notSimpleIndex] = schemeEnd; |
| + } |
| + } |
| + return indices; |
| +} |
| + |
| +bool _isUpperCase(int char) { |
|
floitsch
2016/06/28 00:28:04
unused?
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Indeed. Gone.
|
| + return 0x41 <= char && char <= 0x5b; |
| +} |
| + |
| +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; |
| + |
| + _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 == Uri._SLASH) { |
| + parts.add(_uri.substring(start, i)); |
| + start = i + 1; |
| + } |
| + } |
| + parts.add(_uri.substring(start, end)); |
| + return new List<String>.unmodifiable(parts); |
| + } |
| + |
| + Map<String, String> get queryParameters { |
| + if (!hasQuery) return const <String, String>{}; |
| + return new UnmodifiableMapView<String, String>( |
| + Uri.splitQueryString(query)); |
| + } |
| + |
| + Map<String, List<String>> get queryParametersAll { |
| + if (!hasQuery) return const <String, List<String>>{}; |
| + Map queryParameterLists = Uri._splitQueryStringAll(query); |
| + for (var key in queryParameterLists.keys) { |
| + queryParameterLists[key] = |
| + new List<String>.unmodifiable(queryParameterLists[key]); |
| + } |
| + return new Map<String, List<String>>.unmodifiable(queryParameterLists); |
| + } |
| + |
| + bool _isPort(String port) { |
| + int portDigitStart = _portStart + 1; |
| + return portDigitStart + port.length == _pathStart && |
| + _uri.startsWith(port, portDigitStart); |
| + } |
| + |
| + Uri normalizePath() => this; |
| + |
| + Uri removeFragment() { |
| + if (!hasFragment) return this; |
| + return new _SimpleUri( |
| + _uri.substring(0, _fragmentStart), |
| + _schemeEnd, _hostStart, _portStart, |
| + _pathStart, _queryStart, _fragmentStart, _schemeCache); |
| + } |
| + |
| + Uri replace({String scheme, |
| + String userInfo, |
| + String host, |
| + int port, |
| + String path, |
| + Iterable<String> pathSegments, |
| + String query, |
| + Map<String, dynamic/*String|Iterable<String>*/> queryParameters, |
| + String fragment}) { |
| + bool schemeChanged = false; |
| + if (scheme != null) { |
| + scheme = Uri._makeScheme(scheme, 0, scheme.length); |
| + schemeChanged = !_isScheme(scheme); |
| + } else { |
| + scheme = this.scheme; |
| + } |
| + bool isFile = (scheme == "file"); |
| + if (userInfo != null) { |
| + userInfo = Uri._makeUserInfo(userInfo, 0, userInfo.length); |
| + } else if (_hostStart > 0) { |
| + userInfo = _uri.substring(_schemeEnd + 3, _hostStart); |
| + } else { |
| + userInfo = ""; |
| + } |
| + if (port != null) { |
| + port = Uri._makePort(port, scheme); |
| + } else { |
| + port = this.hasPort ? this.port : null; |
| + if (schemeChanged) { |
| + // The default port might have changed. |
| + port = Uri._makePort(port, scheme); |
| + } |
| + } |
| + if (host != null) { |
| + host = Uri._makeHost(host, 0, host.length, false); |
| + } else if (_hostStart > 0) { |
| + host = _uri.substring(_hostStart, _portStart); |
| + } else if (userInfo.isNotEmpty || port != null || isFile) { |
| + host = ""; |
| + } |
| + |
| + bool hasAuthority = host != null; |
| + if (path != null || pathSegments != null) { |
| + path = Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments, |
| + scheme, hasAuthority); |
| + } else { |
| + path = _uri.substring(_pathStart, _queryStart); |
| + if ((isFile || (hasAuthority && !path.isEmpty)) && |
| + !path.startsWith('/')) { |
| + path = "/" + path; |
| + } |
| + } |
| + |
| + if (query != null || queryParameters != null) { |
| + query = |
| + Uri._makeQuery(query, 0, _stringOrNullLength(query), queryParameters); |
| + } else if (_queryStart < _fragmentStart) { |
| + query = _uri.substring(_queryStart, _fragmentStart); |
| + } |
| + |
| + if (fragment != null) { |
| + fragment = Uri._makeFragment(fragment, 0, fragment.length); |
| + } else if (_fragmentStart < _uri.length) { |
| + fragment = _uri.substring(_fragmentStart); |
| + } |
| + |
| + 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, 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 { |
| + // Slowcase. |
| + 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. |
| + var delta = base._pathStart - ref._pathStart + 1; |
| + 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); |
| + } |
| + // Merge paths. |
| + if (base._uri.startsWith("../", base._pathStart)) { |
| + // Complex rare case, go slow. |
| + return _toNonSimple().resolveUri(ref); |
| + } |
| + 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; |
| + while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) { |
| + refStart += 3; |
| + backCount += 1; |
| + } |
| + if (refStart + 2 == refEnd && refUri.startsWith("..", refStart)) { |
| + refStart += 2; |
| + backCount += 1; |
| + } |
| + |
| + const slash = 0x2f; |
| + while (baseEnd > baseStart) { |
| + baseEnd--; |
| + int char = baseUri.codeUnitAt(baseEnd); |
| + if (char == slash) { |
| + backCount--; |
| + if (backCount == 0) break; |
| + } |
| + } |
| + var delta; |
| + var newUri; |
| + if (baseEnd != baseStart || base._uri.startsWith("/", baseStart) || |
| + base.hasScheme || base.hasAuthority) { |
| + delta = baseEnd - refStart + 1; |
| + newUri = "${base._uri.substring(0, baseEnd)}/" |
| + "${ref._uri.substring(refStart)}"; |
| + } else { |
| + // We exactly removed all of a relative path. |
| + // Example: foo:bar/baz resolve vs. ../../qux -> foo:qux |
| + delta = baseEnd - refStart; |
| + newUri = "${base._uri.substring(0, baseStart)}" |
| + "${ref._uri.substring(refStart)}"; |
| + } |
| + |
| + return new _SimpleUri(newUri, |
| + base._schemeEnd, |
| + base._hostStart, |
| + base._portStart, |
| + base._pathStart, |
| + ref._queryStart + delta, |
| + ref._fragmentStart + delta, |
| + base._schemeCache); |
| + } |
| + |
| + // TODO: Deprecate Remove Kill Crush Destroy! |
|
floitsch
2016/06/28 00:28:03
TODO(ldap).
Also, make this more informative.
Lasse Reichstein Nielsen
2016/06/28 13:00:52
Done. Whoops. :)
|
| + // Reason: requires knowing whether we are on Windows. |
| + // That's a PLATFORM SPECIFIC THING. |
| + // This should be File.pathFromUri, since File is platform specific already. |
| + 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 => _uri.hashCode; |
| + |
| + bool operator==(Object other) { |
| + if (other is! Uri) return false; |
| + return _uri == other.toString(); |
| + } |
| + |
| + 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; |
| +} |