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; |
+} |