OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.core; | 5 part of dart.core; |
6 | 6 |
7 // Frequently used character codes. | 7 // Frequently used character codes. |
8 const int _SPACE = 0x20; | 8 const int _SPACE = 0x20; |
9 const int _PERCENT = 0x25; | 9 const int _PERCENT = 0x25; |
| 10 const int _AMPERSAND = 0x26; |
10 const int _PLUS = 0x2B; | 11 const int _PLUS = 0x2B; |
11 const int _DOT = 0x2E; | 12 const int _DOT = 0x2E; |
12 const int _SLASH = 0x2F; | 13 const int _SLASH = 0x2F; |
13 const int _COLON = 0x3A; | 14 const int _COLON = 0x3A; |
| 15 const int _EQUALS = 0x3d; |
14 const int _UPPER_CASE_A = 0x41; | 16 const int _UPPER_CASE_A = 0x41; |
15 const int _UPPER_CASE_Z = 0x5A; | 17 const int _UPPER_CASE_Z = 0x5A; |
16 const int _LEFT_BRACKET = 0x5B; | 18 const int _LEFT_BRACKET = 0x5B; |
17 const int _BACKSLASH = 0x5C; | 19 const int _BACKSLASH = 0x5C; |
18 const int _RIGHT_BRACKET = 0x5D; | 20 const int _RIGHT_BRACKET = 0x5D; |
19 const int _LOWER_CASE_A = 0x61; | 21 const int _LOWER_CASE_A = 0x61; |
20 const int _LOWER_CASE_F = 0x66; | 22 const int _LOWER_CASE_F = 0x66; |
21 const int _LOWER_CASE_Z = 0x7A; | 23 const int _LOWER_CASE_Z = 0x7A; |
22 | 24 |
23 const String _hexDigits = "0123456789ABCDEF"; | 25 const String _hexDigits = "0123456789ABCDEF"; |
(...skipping 1338 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1362 * The port. Set to null if there is no port. Normalized to null if | 1364 * The port. Set to null if there is no port. Normalized to null if |
1363 * the port is the default port for the scheme. | 1365 * the port is the default port for the scheme. |
1364 */ | 1366 */ |
1365 int _port; | 1367 int _port; |
1366 | 1368 |
1367 /** | 1369 /** |
1368 * The path of the URI. | 1370 * The path of the URI. |
1369 * | 1371 * |
1370 * Always non-null. | 1372 * Always non-null. |
1371 */ | 1373 */ |
1372 String _path; | 1374 final String path; |
1373 | 1375 |
1374 // The query content, or null if there is no query. | 1376 // The query content, or null if there is no query. |
1375 final String _query; | 1377 final String _query; |
1376 | 1378 |
1377 // The fragment content, or null if there is no fragment. | 1379 // The fragment content, or null if there is no fragment. |
1378 final String _fragment; | 1380 final String _fragment; |
1379 | 1381 |
1380 /** | 1382 /** |
1381 * Cache the computed return value of [pathSegments]. | 1383 * Cache the computed return value of [pathSegments]. |
1382 */ | 1384 */ |
(...skipping 11 matching lines...) Expand all Loading... |
1394 */ | 1396 */ |
1395 int _hashCodeCache; | 1397 int _hashCodeCache; |
1396 | 1398 |
1397 /** | 1399 /** |
1398 * Cache the computed return value of [queryParameters]. | 1400 * Cache the computed return value of [queryParameters]. |
1399 */ | 1401 */ |
1400 Map<String, String> _queryParameters; | 1402 Map<String, String> _queryParameters; |
1401 Map<String, List<String>> _queryParameterLists; | 1403 Map<String, List<String>> _queryParameterLists; |
1402 | 1404 |
1403 /// Internal non-verifying constructor. Only call with validated arguments. | 1405 /// Internal non-verifying constructor. Only call with validated arguments. |
| 1406 /// |
| 1407 /// The components must be properly normalized. |
| 1408 /// |
| 1409 /// Use `null` for [_host] if there is no authority. In that case, always |
| 1410 /// pass `null` for [_port] and [_userInfo] as well. |
| 1411 /// |
| 1412 /// Use `null` for [_port], [_userInfo], [_query] and [_fragment] if there is |
| 1413 /// component of that type. |
| 1414 /// |
| 1415 /// The [path] and [scheme] are never empty. |
1404 _Uri._internal(this.scheme, | 1416 _Uri._internal(this.scheme, |
1405 this._userInfo, | 1417 this._userInfo, |
1406 this._host, | 1418 this._host, |
1407 this._port, | 1419 this._port, |
1408 this._path, | 1420 this.path, |
1409 this._query, | 1421 this._query, |
1410 this._fragment); | 1422 this._fragment); |
1411 | 1423 |
1412 /// Create a [_Uri] from parts of [uri]. | 1424 /// Create a [_Uri] from parts of [uri]. |
1413 /// | 1425 /// |
1414 /// The parameters specify the start/end of particular components of the URI. | 1426 /// The parameters specify the start/end of particular components of the URI. |
1415 /// The [scheme] may contain a string representing a normalized scheme | 1427 /// The [scheme] may contain a string representing a normalized scheme |
1416 /// component if one has already been discovered. | 1428 /// component if one has already been discovered. |
1417 factory _Uri.notSimple(String uri, int start, int end, int schemeEnd, | 1429 factory _Uri.notSimple(String uri, int start, int end, int schemeEnd, |
1418 int hostStart, int portStart, int pathStart, | 1430 int hostStart, int portStart, int pathStart, |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1536 return _port; | 1548 return _port; |
1537 } | 1549 } |
1538 | 1550 |
1539 // The default port for the scheme of this Uri. | 1551 // The default port for the scheme of this Uri. |
1540 static int _defaultPort(String scheme) { | 1552 static int _defaultPort(String scheme) { |
1541 if (scheme == "http") return 80; | 1553 if (scheme == "http") return 80; |
1542 if (scheme == "https") return 443; | 1554 if (scheme == "https") return 443; |
1543 return 0; | 1555 return 0; |
1544 } | 1556 } |
1545 | 1557 |
1546 String get path => _path; | |
1547 | |
1548 String get query => _query ?? ""; | 1558 String get query => _query ?? ""; |
1549 | 1559 |
1550 String get fragment => _fragment ?? ""; | 1560 String get fragment => _fragment ?? ""; |
1551 | 1561 |
1552 bool isScheme(String scheme) { | 1562 bool isScheme(String scheme) { |
1553 String thisScheme = this.scheme; | 1563 String thisScheme = this.scheme; |
1554 if (scheme == null) return thisScheme.isEmpty; | 1564 if (scheme == null) return thisScheme.isEmpty; |
1555 if (scheme.length != thisScheme.length) return false; | 1565 if (scheme.length != thisScheme.length) return false; |
1556 return _compareScheme(scheme, thisScheme); | 1566 return _compareScheme(scheme, thisScheme); |
1557 } | 1567 } |
(...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1840 host = this._host; | 1850 host = this._host; |
1841 } else if (userInfo.isNotEmpty || port != null || isFile) { | 1851 } else if (userInfo.isNotEmpty || port != null || isFile) { |
1842 host = ""; | 1852 host = ""; |
1843 } | 1853 } |
1844 | 1854 |
1845 bool hasAuthority = host != null; | 1855 bool hasAuthority = host != null; |
1846 if (path != null || pathSegments != null) { | 1856 if (path != null || pathSegments != null) { |
1847 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, | 1857 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, |
1848 scheme, hasAuthority); | 1858 scheme, hasAuthority); |
1849 } else { | 1859 } else { |
1850 path = this._path; | 1860 path = this.path; |
1851 if ((isFile || (hasAuthority && !path.isEmpty)) && | 1861 if ((isFile || (hasAuthority && !path.isEmpty)) && |
1852 !path.startsWith('/')) { | 1862 !path.startsWith('/')) { |
1853 path = "/" + path; | 1863 path = "/" + path; |
1854 } | 1864 } |
1855 } | 1865 } |
1856 | 1866 |
1857 if (query != null || queryParameters != null) { | 1867 if (query != null || queryParameters != null) { |
1858 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); | 1868 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); |
1859 } else { | 1869 } else { |
1860 query = this._query; | 1870 query = this._query; |
1861 } | 1871 } |
1862 | 1872 |
1863 if (fragment != null) { | 1873 if (fragment != null) { |
1864 fragment = _makeFragment(fragment, 0, fragment.length); | 1874 fragment = _makeFragment(fragment, 0, fragment.length); |
1865 } else { | 1875 } else { |
1866 fragment = this._fragment; | 1876 fragment = this._fragment; |
1867 } | 1877 } |
1868 | 1878 |
1869 return new _Uri._internal( | 1879 return new _Uri._internal( |
1870 scheme, userInfo, host, port, path, query, fragment); | 1880 scheme, userInfo, host, port, path, query, fragment); |
1871 } | 1881 } |
1872 | 1882 |
1873 Uri removeFragment() { | 1883 Uri removeFragment() { |
1874 if (!this.hasFragment) return this; | 1884 if (!this.hasFragment) return this; |
1875 return new _Uri._internal(scheme, _userInfo, _host, _port, | 1885 return new _Uri._internal(scheme, _userInfo, _host, _port, |
1876 _path, _query, null); | 1886 path, _query, null); |
1877 } | 1887 } |
1878 | 1888 |
1879 List<String> get pathSegments { | 1889 List<String> get pathSegments { |
1880 var result = _pathSegments; | 1890 var result = _pathSegments; |
1881 if (result != null) return result; | 1891 if (result != null) return result; |
1882 | 1892 |
1883 var pathToSplit = path; | 1893 var pathToSplit = path; |
1884 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) { | 1894 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) { |
1885 pathToSplit = pathToSplit.substring(1); | 1895 pathToSplit = pathToSplit.substring(1); |
1886 } | 1896 } |
(...skipping 20 matching lines...) Expand all Loading... |
1907 queryParameterLists[key] = | 1917 queryParameterLists[key] = |
1908 new List<String>.unmodifiable(queryParameterLists[key]); | 1918 new List<String>.unmodifiable(queryParameterLists[key]); |
1909 } | 1919 } |
1910 _queryParameterLists = | 1920 _queryParameterLists = |
1911 new Map<String, List<String>>.unmodifiable(queryParameterLists); | 1921 new Map<String, List<String>>.unmodifiable(queryParameterLists); |
1912 } | 1922 } |
1913 return _queryParameterLists; | 1923 return _queryParameterLists; |
1914 } | 1924 } |
1915 | 1925 |
1916 Uri normalizePath() { | 1926 Uri normalizePath() { |
1917 String path = _normalizePath(_path, scheme, hasAuthority); | 1927 String path = _normalizePath(this.path, scheme, hasAuthority); |
1918 if (identical(path, _path)) return this; | 1928 if (identical(path, this.path)) return this; |
1919 return this.replace(path: path); | 1929 return this.replace(path: path); |
1920 } | 1930 } |
1921 | 1931 |
1922 static int _makePort(int port, String scheme) { | 1932 static int _makePort(int port, String scheme) { |
1923 // Perform scheme specific normalization. | 1933 // Perform scheme specific normalization. |
1924 if (port != null && port == _defaultPort(scheme)) return null; | 1934 if (port != null && port == _defaultPort(scheme)) return null; |
1925 return port; | 1935 return port; |
1926 } | 1936 } |
1927 | 1937 |
1928 /** | 1938 /** |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2075 static String _canonicalizeScheme(String scheme) { | 2085 static String _canonicalizeScheme(String scheme) { |
2076 if (scheme == "http") return "http"; | 2086 if (scheme == "http") return "http"; |
2077 if (scheme == "file") return "file"; | 2087 if (scheme == "file") return "file"; |
2078 if (scheme == "https") return "https"; | 2088 if (scheme == "https") return "https"; |
2079 if (scheme == "package") return "package"; | 2089 if (scheme == "package") return "package"; |
2080 return scheme; | 2090 return scheme; |
2081 } | 2091 } |
2082 | 2092 |
2083 static String _makeUserInfo(String userInfo, int start, int end) { | 2093 static String _makeUserInfo(String userInfo, int start, int end) { |
2084 if (userInfo == null) return ""; | 2094 if (userInfo == null) return ""; |
2085 return _normalize(userInfo, start, end, _userinfoTable); | 2095 return _normalizeOrSubstring(userInfo, start, end, _userinfoTable); |
2086 } | 2096 } |
2087 | 2097 |
2088 static String _makePath(String path, int start, int end, | 2098 static String _makePath(String path, int start, int end, |
2089 Iterable<String> pathSegments, | 2099 Iterable<String> pathSegments, |
2090 String scheme, | 2100 String scheme, |
2091 bool hasAuthority) { | 2101 bool hasAuthority) { |
2092 bool isFile = (scheme == "file"); | 2102 bool isFile = (scheme == "file"); |
2093 bool ensureLeadingSlash = isFile || hasAuthority; | 2103 bool ensureLeadingSlash = isFile || hasAuthority; |
2094 if (path == null && pathSegments == null) return isFile ? "/" : ""; | 2104 if (path == null && pathSegments == null) return isFile ? "/" : ""; |
2095 if (path != null && pathSegments != null) { | 2105 if (path != null && pathSegments != null) { |
2096 throw new ArgumentError('Both path and pathSegments specified'); | 2106 throw new ArgumentError('Both path and pathSegments specified'); |
2097 } | 2107 } |
2098 var result; | 2108 var result; |
2099 if (path != null) { | 2109 if (path != null) { |
2100 result = _normalize(path, start, end, _pathCharOrSlashTable); | 2110 result = _normalizeOrSubstring(path, start, end, _pathCharOrSlashTable); |
2101 } else { | 2111 } else { |
2102 result = pathSegments.map((s) => | 2112 result = pathSegments.map((s) => |
2103 _uriEncode(_pathCharTable, s, UTF8, false)).join("/"); | 2113 _uriEncode(_pathCharTable, s, UTF8, false)).join("/"); |
2104 } | 2114 } |
2105 if (result.isEmpty) { | 2115 if (result.isEmpty) { |
2106 if (isFile) return "/"; | 2116 if (isFile) return "/"; |
2107 } else if (ensureLeadingSlash && !result.startsWith('/')) { | 2117 } else if (ensureLeadingSlash && !result.startsWith('/')) { |
2108 result = "/" + result; | 2118 result = "/" + result; |
2109 } | 2119 } |
2110 result = _normalizePath(result, scheme, hasAuthority); | 2120 result = _normalizePath(result, scheme, hasAuthority); |
(...skipping 12 matching lines...) Expand all Loading... |
2123 return _removeDotSegments(path); | 2133 return _removeDotSegments(path); |
2124 } | 2134 } |
2125 | 2135 |
2126 static String _makeQuery( | 2136 static String _makeQuery( |
2127 String query, int start, int end, | 2137 String query, int start, int end, |
2128 Map<String, dynamic/*String|Iterable<String>*/> queryParameters) { | 2138 Map<String, dynamic/*String|Iterable<String>*/> queryParameters) { |
2129 if (query != null) { | 2139 if (query != null) { |
2130 if (queryParameters != null) { | 2140 if (queryParameters != null) { |
2131 throw new ArgumentError('Both query and queryParameters specified'); | 2141 throw new ArgumentError('Both query and queryParameters specified'); |
2132 } | 2142 } |
2133 return _normalize(query, start, end, _queryCharTable); | 2143 return _normalizeOrSubstring(query, start, end, _queryCharTable); |
2134 } | 2144 } |
2135 if (queryParameters == null) return null; | 2145 if (queryParameters == null) return null; |
2136 | 2146 |
2137 var result = new StringBuffer(); | 2147 var result = new StringBuffer(); |
2138 var separator = ""; | 2148 var separator = ""; |
2139 | 2149 |
2140 void writeParameter(String key, String value) { | 2150 void writeParameter(String key, String value) { |
2141 result.write(separator); | 2151 result.write(separator); |
2142 separator = "&"; | 2152 separator = "&"; |
2143 result.write(Uri.encodeQueryComponent(key)); | 2153 result.write(Uri.encodeQueryComponent(key)); |
(...skipping 11 matching lines...) Expand all Loading... |
2155 for (String value in values) { | 2165 for (String value in values) { |
2156 writeParameter(key, value); | 2166 writeParameter(key, value); |
2157 } | 2167 } |
2158 } | 2168 } |
2159 }); | 2169 }); |
2160 return result.toString(); | 2170 return result.toString(); |
2161 } | 2171 } |
2162 | 2172 |
2163 static String _makeFragment(String fragment, int start, int end) { | 2173 static String _makeFragment(String fragment, int start, int end) { |
2164 if (fragment == null) return null; | 2174 if (fragment == null) return null; |
2165 return _normalize(fragment, start, end, _queryCharTable); | 2175 return _normalizeOrSubstring(fragment, start, end, _queryCharTable); |
2166 } | 2176 } |
2167 | 2177 |
2168 /** | 2178 /** |
2169 * Performs RFC 3986 Percent-Encoding Normalization. | 2179 * Performs RFC 3986 Percent-Encoding Normalization. |
2170 * | 2180 * |
2171 * Returns a replacement string that should be replace the original escape. | 2181 * Returns a replacement string that should be replace the original escape. |
2172 * Returns null if no replacement is necessary because the escape is | 2182 * Returns null if no replacement is necessary because the escape is |
2173 * not for an unreserved character and is already non-lower-case. | 2183 * not for an unreserved character and is already non-lower-case. |
2174 * | 2184 * |
2175 * Returns "%" if the escape is invalid (not two valid hex digits following | 2185 * Returns "%" if the escape is invalid (not two valid hex digits following |
2176 * the percent sign). The calling code should replace the percent | 2186 * the percent sign). The calling code should replace the percent |
2177 * sign with "%25", but leave the following two characters unmodified. | 2187 * sign with "%25", but leave the following two characters unmodified. |
2178 * | 2188 * |
2179 * If [lowerCase] is true, a single character returned is always lower case, | 2189 * If [lowerCase] is true, a single character returned is always lower case, |
2180 */ | 2190 */ |
2181 static String _normalizeEscape(String source, int index, bool lowerCase) { | 2191 static String _normalizeEscape(String source, int index, bool lowerCase) { |
2182 assert(source.codeUnitAt(index) == _PERCENT); | 2192 assert(source.codeUnitAt(index) == _PERCENT); |
2183 if (index + 2 >= source.length) { | 2193 if (index + 2 >= source.length) { |
2184 return "%"; // Marks the escape as invalid. | 2194 return "%"; // Marks the escape as invalid. |
2185 } | 2195 } |
2186 int firstDigit = source.codeUnitAt(index + 1); | 2196 int firstDigit = source.codeUnitAt(index + 1); |
2187 int secondDigit = source.codeUnitAt(index + 2); | 2197 int secondDigit = source.codeUnitAt(index + 2); |
2188 int firstDigitValue = _parseHexDigit(firstDigit); | 2198 int firstDigitValue = hexDigitValue(firstDigit); |
2189 int secondDigitValue = _parseHexDigit(secondDigit); | 2199 int secondDigitValue = hexDigitValue(secondDigit); |
2190 if (firstDigitValue < 0 || secondDigitValue < 0) { | 2200 if (firstDigitValue < 0 || secondDigitValue < 0) { |
2191 return "%"; // Marks the escape as invalid. | 2201 return "%"; // Marks the escape as invalid. |
2192 } | 2202 } |
2193 int value = firstDigitValue * 16 + secondDigitValue; | 2203 int value = firstDigitValue * 16 + secondDigitValue; |
2194 if (_isUnreservedChar(value)) { | 2204 if (_isUnreservedChar(value)) { |
2195 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { | 2205 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { |
2196 value |= 0x20; | 2206 value |= 0x20; |
2197 } | 2207 } |
2198 return new String.fromCharCode(value); | 2208 return new String.fromCharCode(value); |
2199 } | 2209 } |
2200 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { | 2210 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { |
2201 // Either digit is lower case. | 2211 // Either digit is lower case. |
2202 return source.substring(index, index + 3).toUpperCase(); | 2212 return source.substring(index, index + 3).toUpperCase(); |
2203 } | 2213 } |
2204 // Escape is retained, and is already non-lower case, so return null to | 2214 // Escape is retained, and is already non-lower case, so return null to |
2205 // represent "no replacement necessary". | 2215 // represent "no replacement necessary". |
2206 return null; | 2216 return null; |
2207 } | 2217 } |
2208 | 2218 |
2209 // Converts a UTF-16 code-unit to its value as a hex digit. | |
2210 // Returns -1 for non-hex digits. | |
2211 static int _parseHexDigit(int char) { | |
2212 const int zeroDigit = 0x30; | |
2213 int digit = char ^ zeroDigit; | |
2214 if (digit <= 9) return digit; | |
2215 int lowerCase = char | 0x20; | |
2216 if (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) { | |
2217 return lowerCase - (_LOWER_CASE_A - 10); | |
2218 } | |
2219 return -1; | |
2220 } | |
2221 | |
2222 static String _escapeChar(int char) { | 2219 static String _escapeChar(int char) { |
2223 assert(char <= 0x10ffff); // It's a valid unicode code point. | 2220 assert(char <= 0x10ffff); // It's a valid unicode code point. |
2224 List<int> codeUnits; | 2221 List<int> codeUnits; |
2225 if (char < 0x80) { | 2222 if (char < 0x80) { |
2226 // ASCII, a single percent encoded sequence. | 2223 // ASCII, a single percent encoded sequence. |
2227 codeUnits = new List(3); | 2224 codeUnits = new List(3); |
2228 codeUnits[0] = _PERCENT; | 2225 codeUnits[0] = _PERCENT; |
2229 codeUnits[1] = _hexDigits.codeUnitAt(char >> 4); | 2226 codeUnits[1] = _hexDigits.codeUnitAt(char >> 4); |
2230 codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf); | 2227 codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf); |
2231 } else { | 2228 } else { |
(...skipping 16 matching lines...) Expand all Loading... |
2248 codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4); | 2245 codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4); |
2249 codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf); | 2246 codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf); |
2250 index += 3; | 2247 index += 3; |
2251 flag = 0x80; // Following bytes have only high bit set. | 2248 flag = 0x80; // Following bytes have only high bit set. |
2252 } | 2249 } |
2253 } | 2250 } |
2254 return new String.fromCharCodes(codeUnits); | 2251 return new String.fromCharCodes(codeUnits); |
2255 } | 2252 } |
2256 | 2253 |
2257 /** | 2254 /** |
| 2255 * Normalizes using [_normalize] or returns substring of original. |
| 2256 * |
| 2257 * If [_normalize] returns `null` (original content is already normalized), |
| 2258 * this methods returns the substring if [component] from [start] to [end]. |
| 2259 */ |
| 2260 static String _normalizeOrSubstring(String component, int start, int end, |
| 2261 List<int> charTable) { |
| 2262 return _normalize(component, start, end, charTable) ?? |
| 2263 component.substring(start, end); |
| 2264 } |
| 2265 |
| 2266 /** |
2258 * Runs through component checking that each character is valid and | 2267 * Runs through component checking that each character is valid and |
2259 * normalize percent escapes. | 2268 * normalize percent escapes. |
2260 * | 2269 * |
2261 * Uses [charTable] to check if a non-`%` character is allowed. | 2270 * Uses [charTable] to check if a non-`%` character is allowed. |
2262 * Each `%` character must be followed by two hex digits. | 2271 * Each `%` character must be followed by two hex digits. |
2263 * If the hex-digits are lower case letters, they are converted to | 2272 * If the hex-digits are lower case letters, they are converted to |
2264 * upper case. | 2273 * upper case. |
| 2274 * |
| 2275 * Returns `null` if the original content was already normalized. |
2265 */ | 2276 */ |
2266 static String _normalize(String component, int start, int end, | 2277 static String _normalize(String component, int start, int end, |
2267 List<int> charTable) { | 2278 List<int> charTable, |
| 2279 {bool escapeDelimiters = false}) { |
2268 StringBuffer buffer; | 2280 StringBuffer buffer; |
2269 int sectionStart = start; | 2281 int sectionStart = start; |
2270 int index = start; | 2282 int index = start; |
2271 // Loop while characters are valid and escapes correct and upper-case. | 2283 // Loop while characters are valid and escapes correct and upper-case. |
2272 while (index < end) { | 2284 while (index < end) { |
2273 int char = component.codeUnitAt(index); | 2285 int char = component.codeUnitAt(index); |
2274 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) { | 2286 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) { |
2275 index++; | 2287 index++; |
2276 } else { | 2288 } else { |
2277 String replacement; | 2289 String replacement; |
2278 int sourceLength; | 2290 int sourceLength; |
2279 if (char == _PERCENT) { | 2291 if (char == _PERCENT) { |
2280 replacement = _normalizeEscape(component, index, false); | 2292 replacement = _normalizeEscape(component, index, false); |
2281 // Returns null if we should keep the existing escape. | 2293 // Returns null if we should keep the existing escape. |
2282 if (replacement == null) { | 2294 if (replacement == null) { |
2283 index += 3; | 2295 index += 3; |
2284 continue; | 2296 continue; |
2285 } | 2297 } |
2286 // Returns "%" if we should escape the existing percent. | 2298 // Returns "%" if we should escape the existing percent. |
2287 if ("%" == replacement) { | 2299 if ("%" == replacement) { |
2288 replacement = "%25"; | 2300 replacement = "%25"; |
2289 sourceLength = 1; | 2301 sourceLength = 1; |
2290 } else { | 2302 } else { |
2291 sourceLength = 3; | 2303 sourceLength = 3; |
2292 } | 2304 } |
2293 } else if (_isGeneralDelimiter(char)) { | 2305 } else if (!escapeDelimiters && _isGeneralDelimiter(char)) { |
2294 _fail(component, index, "Invalid character"); | 2306 _fail(component, index, "Invalid character"); |
2295 } else { | 2307 } else { |
2296 sourceLength = 1; | 2308 sourceLength = 1; |
2297 if ((char & 0xFC00) == 0xD800) { | 2309 if ((char & 0xFC00) == 0xD800) { |
2298 // Possible lead surrogate. | 2310 // Possible lead surrogate. |
2299 if (index + 1 < end) { | 2311 if (index + 1 < end) { |
2300 int tail = component.codeUnitAt(index + 1); | 2312 int tail = component.codeUnitAt(index + 1); |
2301 if ((tail & 0xFC00) == 0xDC00) { | 2313 if ((tail & 0xFC00) == 0xDC00) { |
2302 // Tail surrogate. | 2314 // Tail surrogate. |
2303 sourceLength = 2; | 2315 sourceLength = 2; |
2304 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff); | 2316 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff); |
2305 } | 2317 } |
2306 } | 2318 } |
2307 } | 2319 } |
2308 replacement = _escapeChar(char); | 2320 replacement = _escapeChar(char); |
2309 } | 2321 } |
2310 if (buffer == null) buffer = new StringBuffer(); | 2322 if (buffer == null) buffer = new StringBuffer(); |
2311 buffer.write(component.substring(sectionStart, index)); | 2323 buffer.write(component.substring(sectionStart, index)); |
2312 buffer.write(replacement); | 2324 buffer.write(replacement); |
2313 index += sourceLength; | 2325 index += sourceLength; |
2314 sectionStart = index; | 2326 sectionStart = index; |
2315 } | 2327 } |
2316 } | 2328 } |
2317 if (buffer == null) { | 2329 if (buffer == null) { |
2318 // Makes no copy if start == 0 and end == component.length. | 2330 return null; |
2319 return component.substring(start, end); | |
2320 } | 2331 } |
2321 if (sectionStart < end) { | 2332 if (sectionStart < end) { |
2322 buffer.write(component.substring(sectionStart, end)); | 2333 buffer.write(component.substring(sectionStart, end)); |
2323 } | 2334 } |
2324 return buffer.toString(); | 2335 return buffer.toString(); |
2325 } | 2336 } |
2326 | 2337 |
2327 static bool _isSchemeCharacter(int ch) { | 2338 static bool _isSchemeCharacter(int ch) { |
2328 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 2339 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
2329 } | 2340 } |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2502 targetHost = reference.host; | 2513 targetHost = reference.host; |
2503 targetPort = _makePort(reference.hasPort ? reference.port : null, | 2514 targetPort = _makePort(reference.hasPort ? reference.port : null, |
2504 targetScheme); | 2515 targetScheme); |
2505 targetPath = _removeDotSegments(reference.path); | 2516 targetPath = _removeDotSegments(reference.path); |
2506 if (reference.hasQuery) targetQuery = reference.query; | 2517 if (reference.hasQuery) targetQuery = reference.query; |
2507 } else { | 2518 } else { |
2508 targetUserInfo = this._userInfo; | 2519 targetUserInfo = this._userInfo; |
2509 targetHost = this._host; | 2520 targetHost = this._host; |
2510 targetPort = this._port; | 2521 targetPort = this._port; |
2511 if (reference.path == "") { | 2522 if (reference.path == "") { |
2512 targetPath = this._path; | 2523 targetPath = this.path; |
2513 if (reference.hasQuery) { | 2524 if (reference.hasQuery) { |
2514 targetQuery = reference.query; | 2525 targetQuery = reference.query; |
2515 } else { | 2526 } else { |
2516 targetQuery = this._query; | 2527 targetQuery = this._query; |
2517 } | 2528 } |
2518 } else { | 2529 } else { |
2519 if (reference.hasAbsolutePath) { | 2530 if (reference.hasAbsolutePath) { |
2520 targetPath = _removeDotSegments(reference.path); | 2531 targetPath = _removeDotSegments(reference.path); |
2521 } else { | 2532 } else { |
2522 // This is the RFC 3986 behavior for merging. | 2533 // This is the RFC 3986 behavior for merging. |
2523 if (this.hasEmptyPath) { | 2534 if (this.hasEmptyPath) { |
2524 if (!this.hasAuthority) { | 2535 if (!this.hasAuthority) { |
2525 if (!this.hasScheme) { | 2536 if (!this.hasScheme) { |
2526 // Keep the path relative if no scheme or authority. | 2537 // Keep the path relative if no scheme or authority. |
2527 targetPath = reference.path; | 2538 targetPath = reference.path; |
2528 } else { | 2539 } else { |
2529 // Remove leading dot-segments if the path is put | 2540 // Remove leading dot-segments if the path is put |
2530 // beneath a scheme. | 2541 // beneath a scheme. |
2531 targetPath = _removeDotSegments(reference.path); | 2542 targetPath = _removeDotSegments(reference.path); |
2532 } | 2543 } |
2533 } else { | 2544 } else { |
2534 // RFC algorithm for base with authority and empty path. | 2545 // RFC algorithm for base with authority and empty path. |
2535 targetPath = _removeDotSegments("/" + reference.path); | 2546 targetPath = _removeDotSegments("/" + reference.path); |
2536 } | 2547 } |
2537 } else { | 2548 } else { |
2538 var mergedPath = _mergePaths(this._path, reference.path); | 2549 var mergedPath = _mergePaths(this.path, reference.path); |
2539 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) { | 2550 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) { |
2540 targetPath = _removeDotSegments(mergedPath); | 2551 targetPath = _removeDotSegments(mergedPath); |
2541 } else { | 2552 } else { |
2542 // Non-RFC 3986 behavior. | 2553 // Non-RFC 3986 behavior. |
2543 // If both base and reference are relative paths, | 2554 // If both base and reference are relative paths, |
2544 // allow the merged path to start with "..". | 2555 // allow the merged path to start with "..". |
2545 // The RFC only specifies the case where the base has a scheme. | 2556 // The RFC only specifies the case where the base has a scheme. |
2546 targetPath = _normalizeRelativePath(mergedPath, | 2557 targetPath = _normalizeRelativePath(mergedPath, |
2547 this.hasScheme || this.hasAuthority); | 2558 this.hasScheme || this.hasAuthority); |
2548 } | 2559 } |
(...skipping 16 matching lines...) Expand all Loading... |
2565 bool get hasScheme => scheme.isNotEmpty; | 2576 bool get hasScheme => scheme.isNotEmpty; |
2566 | 2577 |
2567 bool get hasAuthority => _host != null; | 2578 bool get hasAuthority => _host != null; |
2568 | 2579 |
2569 bool get hasPort => _port != null; | 2580 bool get hasPort => _port != null; |
2570 | 2581 |
2571 bool get hasQuery => _query != null; | 2582 bool get hasQuery => _query != null; |
2572 | 2583 |
2573 bool get hasFragment => _fragment != null; | 2584 bool get hasFragment => _fragment != null; |
2574 | 2585 |
2575 bool get hasEmptyPath => _path.isEmpty; | 2586 bool get hasEmptyPath => path.isEmpty; |
2576 | 2587 |
2577 bool get hasAbsolutePath => _path.startsWith('/'); | 2588 bool get hasAbsolutePath => path.startsWith('/'); |
2578 | 2589 |
2579 String get origin { | 2590 String get origin { |
2580 if (scheme == "") { | 2591 if (scheme == "") { |
2581 throw new StateError("Cannot use origin without a scheme: $this"); | 2592 throw new StateError("Cannot use origin without a scheme: $this"); |
2582 } | 2593 } |
2583 if (scheme != "http" && scheme != "https") { | 2594 if (scheme != "http" && scheme != "https") { |
2584 throw new StateError( | 2595 throw new StateError( |
2585 "Origin is only applicable schemes http and https: $this"); | 2596 "Origin is only applicable schemes http and https: $this"); |
2586 } | 2597 } |
2587 if (_host == null || _host == "") { | 2598 if (_host == null || _host == "") { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2645 result.write(host); | 2656 result.write(host); |
2646 result.write(r"\"); | 2657 result.write(r"\"); |
2647 } | 2658 } |
2648 } | 2659 } |
2649 result.writeAll(segments, r"\"); | 2660 result.writeAll(segments, r"\"); |
2650 if (hasDriveLetter && segments.length == 1) result.write(r"\"); | 2661 if (hasDriveLetter && segments.length == 1) result.write(r"\"); |
2651 return result.toString(); | 2662 return result.toString(); |
2652 } | 2663 } |
2653 | 2664 |
2654 bool get _isPathAbsolute { | 2665 bool get _isPathAbsolute { |
2655 return _path != null && _path.startsWith('/'); | 2666 return path != null && path.startsWith('/'); |
2656 } | 2667 } |
2657 | 2668 |
2658 void _writeAuthority(StringSink ss) { | 2669 void _writeAuthority(StringSink ss) { |
2659 if (_userInfo.isNotEmpty) { | 2670 if (_userInfo.isNotEmpty) { |
2660 ss.write(_userInfo); | 2671 ss.write(_userInfo); |
2661 ss.write("@"); | 2672 ss.write("@"); |
2662 } | 2673 } |
2663 if (_host != null) ss.write(_host); | 2674 if (_host != null) ss.write(_host); |
2664 if (_port != null) { | 2675 if (_port != null) { |
2665 ss.write(":"); | 2676 ss.write(":"); |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2735 if (equalsIndex < 0) { | 2746 if (equalsIndex < 0) { |
2736 key = _uriDecode(query, start, end, encoding, true); | 2747 key = _uriDecode(query, start, end, encoding, true); |
2737 value = ""; | 2748 value = ""; |
2738 } else { | 2749 } else { |
2739 key = _uriDecode(query, start, equalsIndex, encoding, true); | 2750 key = _uriDecode(query, start, equalsIndex, encoding, true); |
2740 value = _uriDecode(query, equalsIndex + 1, end, encoding, true); | 2751 value = _uriDecode(query, equalsIndex + 1, end, encoding, true); |
2741 } | 2752 } |
2742 result.putIfAbsent(key, _createList).add(value); | 2753 result.putIfAbsent(key, _createList).add(value); |
2743 } | 2754 } |
2744 | 2755 |
2745 const int _equals = 0x3d; | |
2746 const int _ampersand = 0x26; | |
2747 while (i < query.length) { | 2756 while (i < query.length) { |
2748 int char = query.codeUnitAt(i); | 2757 int char = query.codeUnitAt(i); |
2749 if (char == _equals) { | 2758 if (char == _EQUALS) { |
2750 if (equalsIndex < 0) equalsIndex = i; | 2759 if (equalsIndex < 0) equalsIndex = i; |
2751 } else if (char == _ampersand) { | 2760 } else if (char == _AMPERSAND) { |
2752 parsePair(start, equalsIndex, i); | 2761 parsePair(start, equalsIndex, i); |
2753 start = i + 1; | 2762 start = i + 1; |
2754 equalsIndex = -1; | 2763 equalsIndex = -1; |
2755 } | 2764 } |
2756 i++; | 2765 i++; |
2757 } | 2766 } |
2758 parsePair(start, equalsIndex, i); | 2767 parsePair(start, equalsIndex, i); |
2759 return result; | 2768 return result; |
2760 } | 2769 } |
2761 | 2770 |
(...skipping 380 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3142 * `data:` scheme or not. | 3151 * `data:` scheme or not. |
3143 * | 3152 * |
3144 * The first speparator ends the mime type. We don't bother with finding | 3153 * The first speparator ends the mime type. We don't bother with finding |
3145 * the '/' inside the mime type. | 3154 * the '/' inside the mime type. |
3146 * | 3155 * |
3147 * Each two separators after that marks a parameter key and value. | 3156 * Each two separators after that marks a parameter key and value. |
3148 * | 3157 * |
3149 * If there is a single separator left, it ends the "base64" marker. | 3158 * If there is a single separator left, it ends the "base64" marker. |
3150 * | 3159 * |
3151 * So the following separators are found for a text: | 3160 * So the following separators are found for a text: |
3152 * | 3161 * ``` |
3153 * data:text/plain;foo=bar;base64,ARGLEBARGLE= | 3162 * data:text/plain;foo=bar;base64,ARGLEBARGLE= |
3154 * ^ ^ ^ ^ ^ | 3163 * ^ ^ ^ ^ ^ |
3155 * | 3164 * ``` |
3156 */ | 3165 */ |
3157 final List<int> _separatorIndices; | 3166 final List<int> _separatorIndices; |
3158 | 3167 |
3159 /** | 3168 /** |
3160 * Cache of the result returned by [uri]. | 3169 * Cache of the result returned by [uri]. |
3161 */ | 3170 */ |
3162 Uri _uriCache; | 3171 Uri _uriCache; |
3163 | 3172 |
3164 UriData._(this._text, this._separatorIndices, this._uriCache); | 3173 UriData._(this._text, this._separatorIndices, this._uriCache); |
3165 | 3174 |
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3346 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat
a | 3355 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat
a |
3347 * ```` | 3356 * ```` |
3348 * | 3357 * |
3349 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045, | 3358 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045, |
3350 * and `data` is a sequence of URI-characters (RFC-2396 `uric`). | 3359 * and `data` is a sequence of URI-characters (RFC-2396 `uric`). |
3351 * | 3360 * |
3352 * This means that all the characters must be ASCII, but the URI may contain | 3361 * This means that all the characters must be ASCII, but the URI may contain |
3353 * percent-escapes for non-ASCII byte values that need an interpretation | 3362 * percent-escapes for non-ASCII byte values that need an interpretation |
3354 * to be converted to the corresponding string. | 3363 * to be converted to the corresponding string. |
3355 * | 3364 * |
3356 * Parsing doesn't check the validity of any part, it just checks that the | 3365 * Parsing checks that Base64 encoded data is valid, and it normalizes it |
3357 * input has the correct structure with the correct sequence of `/`, `;`, `=` | 3366 * to use the default Base64 alphabet and to use padding. |
3358 * and `,` delimiters. | 3367 * Non-Base64 data is escaped using percent-escapes as necessary to make |
| 3368 * it valid, and existing escapes are case normalized. |
3359 * | 3369 * |
3360 * Accessing the individual parts may fail later if they turn out to have | 3370 * Accessing the individual parts may fail later if they turn out to have |
3361 * content that can't be decoded successfully as a string. | 3371 * content that can't be decoded successfully as a string, for example if |
| 3372 * existing percent escapes represent bytes that cannot be decoded |
| 3373 * by the chosen [Encoding] (see [contentAsString]). |
3362 */ | 3374 */ |
3363 static UriData parse(String uri) { | 3375 static UriData parse(String uri) { |
3364 if (uri.length >= 5) { | 3376 if (uri.length >= 5) { |
3365 int dataDelta = _startsWithData(uri, 0); | 3377 int dataDelta = _startsWithData(uri, 0); |
3366 if (dataDelta == 0) { | 3378 if (dataDelta == 0) { |
3367 // Exact match on "data:". | 3379 // Exact match on "data:". |
3368 return _parse(uri, 5, null); | 3380 return _parse(uri, 5, null); |
3369 } | 3381 } |
3370 if (dataDelta == 0x20) { | 3382 if (dataDelta == 0x20) { |
3371 // Starts with a non-normalized "data" scheme containing upper-case | 3383 // Starts with a non-normalized "data" scheme containing upper-case |
3372 // letters. Parse anyway, but throw away the scheme. | 3384 // letters. Parse anyway, but throw away the scheme. |
3373 return _parse(uri.substring(5), 0, null); | 3385 return _parse(uri.substring(5), 0, null); |
3374 } | 3386 } |
3375 } | 3387 } |
3376 throw new FormatException("Does not start with 'data:'", uri, 0); | 3388 throw new FormatException("Does not start with 'data:'", uri, 0); |
3377 } | 3389 } |
3378 | 3390 |
3379 /** | 3391 /** |
3380 * The [Uri] that this `UriData` is giving access to. | 3392 * The [Uri] that this `UriData` is giving access to. |
3381 * | 3393 * |
3382 * Returns a `Uri` with scheme `data` and the remainder of the data URI | 3394 * Returns a `Uri` with scheme `data` and the remainder of the data URI |
3383 * as path. | 3395 * as path. |
3384 */ | 3396 */ |
3385 Uri get uri { | 3397 Uri get uri { |
3386 if (_uriCache != null) return _uriCache; | 3398 if (_uriCache != null) return _uriCache; |
3387 String path = _text; | 3399 String path = _text; |
3388 String query = null; | 3400 String query = null; |
3389 int colonIndex = _separatorIndices[0]; | 3401 int colonIndex = _separatorIndices[0]; |
3390 int queryIndex = _text.indexOf('?', colonIndex + 1); | 3402 int queryIndex = _text.indexOf('?', colonIndex + 1); |
3391 int end = null; | 3403 int end = _text.length; |
3392 if (queryIndex >= 0) { | 3404 if (queryIndex >= 0) { |
3393 query = _text.substring(queryIndex + 1); | 3405 query = _Uri._normalizeOrSubstring(_text, queryIndex + 1, end, _Uri._query
CharTable); |
3394 end = queryIndex; | 3406 end = queryIndex; |
3395 } | 3407 } |
3396 path = _text.substring(colonIndex + 1, end); | 3408 path = _Uri._normalizeOrSubstring(_text, colonIndex + 1, end, |
3397 // TODO(lrn): This can generate a URI that isn't path normalized. | 3409 _Uri._pathCharOrSlashTable); |
3398 // That's perfectly reasonable - data URIs are not hierarchical, | 3410 _uriCache = new _DataUri(this, path, query); |
3399 // but it may make some consumers stumble. | |
3400 // Should we at least do escape normalization? | |
3401 _uriCache = new _Uri._internal("data", "", null, null, path, query, null); | |
3402 return _uriCache; | 3411 return _uriCache; |
3403 } | 3412 } |
3404 | 3413 |
3405 /** | 3414 /** |
3406 * The MIME type of the data URI. | 3415 * The MIME type of the data URI. |
3407 * | 3416 * |
3408 * A data URI consists of a "media type" followed by data. | 3417 * A data URI consists of a "media type" followed by data. |
3409 * The media type starts with a MIME type and can be followed by | 3418 * The media type starts with a MIME type and can be followed by |
3410 * extra parameters. | 3419 * extra parameters. |
| 3420 * If the MIME type representation in the URI text contains URI escapes, |
| 3421 * they are unescaped in the returned string. |
| 3422 * If the value contain non-ASCII percent escapes, they are decoded as UTF-8. |
3411 * | 3423 * |
3412 * Example: | 3424 * Example: |
3413 * | 3425 * |
3414 * data:text/plain;charset=utf-8,Hello%20World! | 3426 * data:text/plain;charset=utf-8,Hello%20World! |
3415 * | 3427 * |
3416 * This data URI has the media type `text/plain;charset=utf-8`, which is the | 3428 * This data URI has the media type `text/plain;charset=utf-8`, which is the |
3417 * MIME type `text/plain` with the parameter `charset` with value `utf-8`. | 3429 * MIME type `text/plain` with the parameter `charset` with value `utf-8`. |
3418 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. | 3430 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. |
3419 * | 3431 * |
3420 * If the first part of the data URI is empty, it defaults to `text/plain`. | 3432 * If the first part of the data URI is empty, it defaults to `text/plain`. |
3421 */ | 3433 */ |
3422 String get mimeType { | 3434 String get mimeType { |
3423 int start = _separatorIndices[0] + 1; | 3435 int start = _separatorIndices[0] + 1; |
3424 int end = _separatorIndices[1]; | 3436 int end = _separatorIndices[1]; |
3425 if (start == end) return "text/plain"; | 3437 if (start == end) return "text/plain"; |
3426 return _Uri._uriDecode(_text, start, end, UTF8, false); | 3438 return _Uri._uriDecode(_text, start, end, UTF8, false); |
3427 } | 3439 } |
3428 | 3440 |
3429 /** | 3441 /** |
3430 * The charset parameter of the media type. | 3442 * The charset parameter of the media type. |
3431 * | 3443 * |
3432 * If the parameters of the media type contains a `charset` parameter | 3444 * If the parameters of the media type contains a `charset` parameter |
3433 * then this returns its value, otherwise it returns `US-ASCII`, | 3445 * then this returns its value, otherwise it returns `US-ASCII`, |
3434 * which is the default charset for data URIs. | 3446 * which is the default charset for data URIs. |
| 3447 * If the value contain non-ASCII percent escapes, they are decoded as UTF-8. |
| 3448 * |
| 3449 * If the MIME type representation in the URI text contains URI escapes, |
| 3450 * they are unescaped in the returned string. |
3435 */ | 3451 */ |
3436 String get charset { | 3452 String get charset { |
3437 int parameterStart = 1; | 3453 int parameterStart = 1; |
3438 int parameterEnd = _separatorIndices.length - 1; // The ',' before data. | 3454 int parameterEnd = _separatorIndices.length - 1; // The ',' before data. |
3439 if (isBase64) { | 3455 if (isBase64) { |
3440 // There is a ";base64" separator, so subtract one for that as well. | 3456 // There is a ";base64" separator, so subtract one for that as well. |
3441 parameterEnd -= 1; | 3457 parameterEnd -= 1; |
3442 } | 3458 } |
3443 for (int i = parameterStart; i < parameterEnd; i += 2) { | 3459 for (int i = parameterStart; i < parameterEnd; i += 2) { |
3444 var keyStart = _separatorIndices[i] + 1; | 3460 var keyStart = _separatorIndices[i] + 1; |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3496 result.setRange(0, length, text.codeUnits, start); | 3512 result.setRange(0, length, text.codeUnits, start); |
3497 return result; | 3513 return result; |
3498 } | 3514 } |
3499 int index = 0; | 3515 int index = 0; |
3500 for (int i = start; i < text.length; i++) { | 3516 for (int i = start; i < text.length; i++) { |
3501 var codeUnit = text.codeUnitAt(i); | 3517 var codeUnit = text.codeUnitAt(i); |
3502 if (codeUnit != percent) { | 3518 if (codeUnit != percent) { |
3503 result[index++] = codeUnit; | 3519 result[index++] = codeUnit; |
3504 } else { | 3520 } else { |
3505 if (i + 2 < text.length) { | 3521 if (i + 2 < text.length) { |
3506 var digit1 = _Uri._parseHexDigit(text.codeUnitAt(i + 1)); | 3522 int byte = parseHexByte(text, i + 1); |
3507 var digit2 = _Uri._parseHexDigit(text.codeUnitAt(i + 2)); | 3523 if (byte >= 0) { |
3508 if (digit1 >= 0 && digit2 >= 0) { | |
3509 int byte = digit1 * 16 + digit2; | |
3510 result[index++] = byte; | 3524 result[index++] = byte; |
3511 i += 2; | 3525 i += 2; |
3512 continue; | 3526 continue; |
3513 } | 3527 } |
3514 } | 3528 } |
3515 throw new FormatException("Invalid percent escape", text, i); | 3529 throw new FormatException("Invalid percent escape", text, i); |
3516 } | 3530 } |
3517 } | 3531 } |
3518 assert(index == result.length); | 3532 assert(index == result.length); |
3519 return result; | 3533 return result; |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3554 * A map representing the parameters of the media type. | 3568 * A map representing the parameters of the media type. |
3555 * | 3569 * |
3556 * A data URI may contain parameters between the MIME type and the | 3570 * A data URI may contain parameters between the MIME type and the |
3557 * data. This converts these parameters to a map from parameter name | 3571 * data. This converts these parameters to a map from parameter name |
3558 * to parameter value. | 3572 * to parameter value. |
3559 * The map only contains parameters that actually occur in the URI. | 3573 * The map only contains parameters that actually occur in the URI. |
3560 * The `charset` parameter has a default value even if it doesn't occur | 3574 * The `charset` parameter has a default value even if it doesn't occur |
3561 * in the URI, which is reflected by the [charset] getter. This means that | 3575 * in the URI, which is reflected by the [charset] getter. This means that |
3562 * [charset] may return a value even if `parameters["charset"]` is `null`. | 3576 * [charset] may return a value even if `parameters["charset"]` is `null`. |
3563 * | 3577 * |
3564 * If the values contain non-ASCII values or percent escapes, they default | 3578 * If the values contain non-ASCII values or percent escapes, |
3565 * to being decoded as UTF-8. | 3579 * they are decoded as UTF-8. |
3566 */ | 3580 */ |
3567 Map<String, String> get parameters { | 3581 Map<String, String> get parameters { |
3568 var result = <String, String>{}; | 3582 var result = <String, String>{}; |
3569 for (int i = 3; i < _separatorIndices.length; i += 2) { | 3583 for (int i = 3; i < _separatorIndices.length; i += 2) { |
3570 var start = _separatorIndices[i - 2] + 1; | 3584 var start = _separatorIndices[i - 2] + 1; |
3571 var equals = _separatorIndices[i - 1]; | 3585 var equals = _separatorIndices[i - 1]; |
3572 var end = _separatorIndices[i]; | 3586 var end = _separatorIndices[i]; |
3573 String key = _Uri._uriDecode(_text, start, equals, UTF8, false); | 3587 String key = _Uri._uriDecode(_text, start, equals, UTF8, false); |
3574 String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false); | 3588 String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false); |
3575 result[key] = value; | 3589 result[key] = value; |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3626 var lastSeparator = indices.last; | 3640 var lastSeparator = indices.last; |
3627 if (char != comma || | 3641 if (char != comma || |
3628 i != lastSeparator + 7 /* "base64,".length */ || | 3642 i != lastSeparator + 7 /* "base64,".length */ || |
3629 !text.startsWith("base64", lastSeparator + 1)) { | 3643 !text.startsWith("base64", lastSeparator + 1)) { |
3630 throw new FormatException("Expecting '='", text, i); | 3644 throw new FormatException("Expecting '='", text, i); |
3631 } | 3645 } |
3632 break; | 3646 break; |
3633 } | 3647 } |
3634 } | 3648 } |
3635 indices.add(i); | 3649 indices.add(i); |
| 3650 bool isBase64 = indices.length.isOdd; |
| 3651 if (isBase64) { |
| 3652 text = BASE64.normalize(text, i + 1, text.length); |
| 3653 } else { |
| 3654 // Validate "data" part, must only contain RFC 2396 'uric' characters |
| 3655 // (reserved, unreserved, or escape sequences). |
| 3656 // Normalize to this (throws on a fragment separator). |
| 3657 var data = _Uri._normalize(text, i + 1, text.length, _uricTable, |
| 3658 escapeDelimiters: true); |
| 3659 if (data != null) { |
| 3660 text = text.replaceRange(i + 1, text.length, data); |
| 3661 } |
| 3662 } |
3636 return new UriData._(text, indices, sourceUri); | 3663 return new UriData._(text, indices, sourceUri); |
3637 } | 3664 } |
3638 | 3665 |
3639 /** | 3666 /** |
3640 * Like [Uri._uriEncode] but takes the input as bytes, not a string. | 3667 * Like [Uri._uriEncode] but takes the input as bytes, not a string. |
3641 * | 3668 * |
3642 * Encodes into [buffer] instead of creating its own buffer. | 3669 * Encodes into [buffer] instead of creating its own buffer. |
3643 */ | 3670 */ |
3644 static void _uriEncodeBytes(List<int> canonicalTable, | 3671 static void _uriEncodeBytes(List<int> canonicalTable, |
3645 List<int> bytes, | 3672 List<int> bytes, |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3699 | 3726 |
3700 // All non-escape RFC-2396 uric characters. | 3727 // All non-escape RFC-2396 uric characters. |
3701 // | 3728 // |
3702 // uric = reserved | unreserved | escaped | 3729 // uric = reserved | unreserved | escaped |
3703 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," | 3730 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," |
3704 // unreserved = alphanum | mark | 3731 // unreserved = alphanum | mark |
3705 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" | 3732 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" |
3706 // | 3733 // |
3707 // This is the same characters as in a URI query (which is URI pchar plus '?') | 3734 // This is the same characters as in a URI query (which is URI pchar plus '?') |
3708 static const _uricTable = _Uri._queryCharTable; | 3735 static const _uricTable = _Uri._queryCharTable; |
| 3736 |
| 3737 // Characters allowed in base-64 encoding (alphanumeric, '/', '+' and '='). |
| 3738 static const _base64Table = const [ |
| 3739 // LSB MSB |
| 3740 // | | |
| 3741 0x0000, // 0x00 - 0x0f 00000000 00000000 |
| 3742 0x0000, // 0x10 - 0x1f 00000000 00000000 |
| 3743 // + / |
| 3744 0x8800, // 0x20 - 0x2f 00000000 00010001 |
| 3745 // 01234567 89 |
| 3746 0x03ff, // 0x30 - 0x3f 11111111 11000000 |
| 3747 // ABCDEFG HIJKLMNO |
| 3748 0xfffe, // 0x40 - 0x4f 01111111 11111111 |
| 3749 // PQRSTUVW XYZ |
| 3750 0x07ff, // 0x50 - 0x5f 11111111 11100000 |
| 3751 // abcdefg hijklmno |
| 3752 0xfffe, // 0x60 - 0x6f 01111111 11111111 |
| 3753 // pqrstuvw xyz |
| 3754 0x07ff, // 0x70 - 0x7f 11111111 11100000 |
| 3755 ]; |
3709 } | 3756 } |
3710 | 3757 |
3711 // -------------------------------------------------------------------- | 3758 // -------------------------------------------------------------------- |
3712 // Constants used to read the scanner result. | 3759 // Constants used to read the scanner result. |
3713 // The indices points into the table filled by [_scan] which contains | 3760 // The indices points into the table filled by [_scan] which contains |
3714 // recognized positions in the scanned URI. | 3761 // recognized positions in the scanned URI. |
3715 // The `0` index is only used internally. | 3762 // The `0` index is only used internally. |
3716 | 3763 |
3717 /// Index of the position of that `:` after a scheme. | 3764 /// Index of the position of that `:` after a scheme. |
3718 const int _schemeEndIndex = 1; | 3765 const int _schemeEndIndex = 1; |
(...skipping 829 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4548 this.hasPort ? this.port : null, | 4595 this.hasPort ? this.port : null, |
4549 this.path, | 4596 this.path, |
4550 this.hasQuery ? this.query : null, | 4597 this.hasQuery ? this.query : null, |
4551 this.hasFragment ? this.fragment : null | 4598 this.hasFragment ? this.fragment : null |
4552 ); | 4599 ); |
4553 } | 4600 } |
4554 | 4601 |
4555 String toString() => _uri; | 4602 String toString() => _uri; |
4556 } | 4603 } |
4557 | 4604 |
| 4605 /// Special [_Uri] created from an existing [UriData]. |
| 4606 class _DataUri extends _Uri { |
| 4607 final UriData _data; |
| 4608 |
| 4609 _DataUri(this._data, String path, String query) |
| 4610 : super._internal("data", null, null, null, path, query, null); |
| 4611 |
| 4612 UriData get data => _data; |
| 4613 } |
| 4614 |
4558 /// Checks whether [text] starts with "data:" at position [start]. | 4615 /// Checks whether [text] starts with "data:" at position [start]. |
4559 /// | 4616 /// |
4560 /// The text must be long enough to allow reading five characters | 4617 /// The text must be long enough to allow reading five characters |
4561 /// from the [start] position. | 4618 /// from the [start] position. |
4562 /// | 4619 /// |
4563 /// Returns an integer value which is zero if text starts with all-lowercase | 4620 /// Returns an integer value which is zero if text starts with all-lowercase |
4564 /// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case. | 4621 /// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case. |
4565 /// All other values means the text starts with some other character. | 4622 /// All other values means the text starts with some other character. |
4566 int _startsWithData(String text, int start) { | 4623 int _startsWithData(String text, int start) { |
4567 // Multiply by 3 to avoid a non-colon character making delta be 0x20. | 4624 // Multiply by 3 to avoid a non-colon character making delta be 0x20. |
4568 int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3; | 4625 int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3; |
4569 delta |= text.codeUnitAt(start) ^ 0x64 /*d*/; | 4626 delta |= text.codeUnitAt(start) ^ 0x64 /*d*/; |
4570 delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/; | 4627 delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/; |
4571 delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/; | 4628 delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/; |
4572 delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/; | 4629 delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/; |
4573 return delta; | 4630 return delta; |
4574 } | 4631 } |
4575 | 4632 |
4576 /// Helper function returning the length of a string, or `0` for `null`. | 4633 /// Helper function returning the length of a string, or `0` for `null`. |
4577 int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; | 4634 int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; |
OLD | NEW |