Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 2694373003: Normalize UriData.parse result. (Closed)
Patch Set: Address comments. Fix bug. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sdk/lib/convert/convert.dart ('k') | sdk/lib/internal/internal.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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;
OLDNEW
« no previous file with comments | « sdk/lib/convert/convert.dart ('k') | sdk/lib/internal/internal.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698