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 /** | 7 /** |
8 * A parsed URI, such as a URL. | 8 * A parsed URI, such as a URL. |
9 * | 9 * |
10 * **See also:** | 10 * **See also:** |
(...skipping 727 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
738 * then a slash is added to the returned URI's path. | 738 * then a slash is added to the returned URI's path. |
739 * In all other cases, the result is the same as returned by `Uri.file`. | 739 * In all other cases, the result is the same as returned by `Uri.file`. |
740 */ | 740 */ |
741 factory Uri.directory(String path, {bool windows}) { | 741 factory Uri.directory(String path, {bool windows}) { |
742 windows = (windows == null) ? Uri._isWindows : windows; | 742 windows = (windows == null) ? Uri._isWindows : windows; |
743 return windows ? _makeWindowsFileUrl(path, true) | 743 return windows ? _makeWindowsFileUrl(path, true) |
744 : _makeFileUri(path, true); | 744 : _makeFileUri(path, true); |
745 } | 745 } |
746 | 746 |
747 /** | 747 /** |
748 * Creates a `data:` URI containing the [content] string. | |
749 * | |
750 * Converts the content to a bytes using [encoding] or the charset specified | |
751 * in [parameters] (defaulting to US-ASCII if not specified or unrecognized), | |
752 * then encodes the bytes into the resulting data URI. | |
753 * | |
754 * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid | |
755 * bytes is replaced by a percent encoding). If [base64] is true, the bytes | |
756 * are instead encoded using [BASE64]. | |
757 * | |
758 * If [encoding] is not provided and [parameters] has a `charset` entry, | |
759 * that name is looked up using [Encoding.getByName], | |
760 * and if the lookup returns an encoding, that encoding is used to convert | |
761 * [content] to bytes. | |
762 * If providing both an [encoding] and a charset [parameter], they should | |
763 * agree, otherwise decoding won't be able to use the charset parameter | |
764 * to determine the encoding. | |
765 * | |
766 * If [mimeType] and/or [parameters] are supplied, they are added to the | |
767 * created URI. If any of these contain characters that are not allowed | |
768 * in the data URI, the character is percent-escaped. If the character is | |
769 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent | |
770 * encoded. An omitted [mimeType] in a data URI means `text/plain`, just | |
771 * as an omitted `charset` parameter defaults to meaning `US-ASCII`. | |
772 * | |
773 * To read the content back, use [UriData.contentAsString]. | |
774 */ | |
775 factory Uri.dataFromString(String content, | |
776 {String mimeType, | |
777 Encoding encoding, | |
778 Map<String, String> parameters, | |
779 bool base64: false}) { | |
780 return new UriData.fromString(content, | |
781 mimeType: mimeType, | |
782 encoding: encoding, | |
783 parameters: parameters, | |
784 base64: base64).uri; | |
floitsch
2015/11/07 02:56:26
move ".uri" to the next line, so it's visible.
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
| |
785 } | |
786 | |
787 /** | |
788 * Creates a `data:` URI containing an encoding of [bytes]. | |
789 * | |
790 * Defaults to Base64 encoding the bytes, but if [percentEncoded] | |
791 * is `true`, the bytes will instead be percent encoded (any non-ASCII | |
792 * or non-valid-ASCII-character byte is replaced by a percent encoding). | |
793 * | |
794 * To read the bytes back, use [UriData.contentAsBytes]. | |
795 * | |
796 * It defaults to having the mime-type `application/octet-stream`. | |
797 * The [mimeType] and [parameters] are added to the created URI. | |
798 * If any of these contain characters that are not allowed | |
799 * in the data URI, the character is percent-escaped. If the character is | |
800 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent | |
801 * encoded. | |
802 */ | |
803 factory Uri.dataFromBytes(List<int> bytes, | |
804 {mimeType: "application/octet-stream", | |
805 Map<String, String> parameters, | |
806 percentEncoded: false}) { | |
807 return new UriData.fromBytes(bytes, | |
808 mimeType: mimeType, | |
809 parameters: parameters, | |
810 percentEncoded: percentEncoded).uri; | |
floitsch
2015/11/07 02:56:27
ditto.
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
| |
811 } | |
812 | |
813 /** | |
748 * Returns the natural base URI for the current platform. | 814 * Returns the natural base URI for the current platform. |
749 * | 815 * |
750 * When running in a browser this is the current URL (from | 816 * When running in a browser this is the current URL (from |
751 * `window.location.href`). | 817 * `window.location.href`). |
752 * | 818 * |
753 * When not running in a browser this is the file URI referencing | 819 * When not running in a browser this is the file URI referencing |
754 * the current working directory. | 820 * the current working directory. |
755 */ | 821 */ |
756 external static Uri get base; | 822 external static Uri get base; |
757 | 823 |
(...skipping 533 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1291 return result.toString(); | 1357 return result.toString(); |
1292 } | 1358 } |
1293 | 1359 |
1294 static String _makeFragment(String fragment, int start, int end) { | 1360 static String _makeFragment(String fragment, int start, int end) { |
1295 if (fragment == null) return null; | 1361 if (fragment == null) return null; |
1296 return _normalize(fragment, start, end, _queryCharTable); | 1362 return _normalize(fragment, start, end, _queryCharTable); |
1297 } | 1363 } |
1298 | 1364 |
1299 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; | 1365 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; |
1300 | 1366 |
1301 static bool _isHexDigit(int char) { | |
1302 if (_NINE >= char) return _ZERO <= char; | |
1303 char |= 0x20; | |
1304 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char; | |
1305 } | |
1306 | |
1307 static int _hexValue(int char) { | |
1308 assert(_isHexDigit(char)); | |
1309 if (_NINE >= char) return char - _ZERO; | |
1310 char |= 0x20; | |
1311 return char - (_LOWER_CASE_A - 10); | |
1312 } | |
1313 | |
1314 /** | 1367 /** |
1315 * Performs RFC 3986 Percent-Encoding Normalization. | 1368 * Performs RFC 3986 Percent-Encoding Normalization. |
1316 * | 1369 * |
1317 * Returns a replacement string that should be replace the original escape. | 1370 * Returns a replacement string that should be replace the original escape. |
1318 * Returns null if no replacement is necessary because the escape is | 1371 * Returns null if no replacement is necessary because the escape is |
1319 * not for an unreserved character and is already non-lower-case. | 1372 * not for an unreserved character and is already non-lower-case. |
1320 * | 1373 * |
1321 * Returns "%" if the escape is invalid (not two valid hex digits following | 1374 * Returns "%" if the escape is invalid (not two valid hex digits following |
1322 * the percent sign). The calling code should replace the percent | 1375 * the percent sign). The calling code should replace the percent |
1323 * sign with "%25", but leave the following two characters unmodified. | 1376 * sign with "%25", but leave the following two characters unmodified. |
1324 * | 1377 * |
1325 * If [lowerCase] is true, a single character returned is always lower case, | 1378 * If [lowerCase] is true, a single character returned is always lower case, |
1326 */ | 1379 */ |
1327 static String _normalizeEscape(String source, int index, bool lowerCase) { | 1380 static String _normalizeEscape(String source, int index, bool lowerCase) { |
1328 assert(source.codeUnitAt(index) == _PERCENT); | 1381 assert(source.codeUnitAt(index) == _PERCENT); |
1329 if (index + 2 >= source.length) { | 1382 if (index + 2 >= source.length) { |
1330 return "%"; // Marks the escape as invalid. | 1383 return "%"; // Marks the escape as invalid. |
1331 } | 1384 } |
1332 int firstDigit = source.codeUnitAt(index + 1); | 1385 int firstDigit = source.codeUnitAt(index + 1); |
1333 int secondDigit = source.codeUnitAt(index + 2); | 1386 int secondDigit = source.codeUnitAt(index + 2); |
1334 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) { | 1387 int firstDigitValue = _parseHexDigit(firstDigit); |
1388 int secondDigitValue = _parseHexDigit(secondDigit); | |
1389 if (firstDigitValue < 0 || secondDigitValue < 0) { | |
1335 return "%"; // Marks the escape as invalid. | 1390 return "%"; // Marks the escape as invalid. |
1336 } | 1391 } |
1337 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit); | 1392 int value = firstDigitValue * 16 + secondDigitValue; |
1338 if (_isUnreservedChar(value)) { | 1393 if (_isUnreservedChar(value)) { |
1339 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { | 1394 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { |
1340 value |= 0x20; | 1395 value |= 0x20; |
1341 } | 1396 } |
1342 return new String.fromCharCode(value); | 1397 return new String.fromCharCode(value); |
1343 } | 1398 } |
1344 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { | 1399 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { |
1345 // Either digit is lower case. | 1400 // Either digit is lower case. |
1346 return source.substring(index, index + 3).toUpperCase(); | 1401 return source.substring(index, index + 3).toUpperCase(); |
1347 } | 1402 } |
1348 // Escape is retained, and is already non-lower case, so return null to | 1403 // Escape is retained, and is already non-lower case, so return null to |
1349 // represent "no replacement necessary". | 1404 // represent "no replacement necessary". |
1350 return null; | 1405 return null; |
1351 } | 1406 } |
1352 | 1407 |
1353 static bool _isUnreservedChar(int ch) { | 1408 // Converts a UTF-16 code-unit to its value as a hex digit. |
1354 return ch < 127 && | 1409 // Returns -1 for non-hex digits. |
1355 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 1410 static int _parseHexDigit(int char) { |
1411 int digit = char ^ Uri._ZERO; | |
1412 if (digit <= 9) return digit; | |
1413 int lowerCase = char | 0x20; | |
1414 if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) { | |
1415 return lowerCase - (_LOWER_CASE_A - 10); | |
1416 } | |
1417 return -1; | |
1356 } | 1418 } |
1357 | 1419 |
1358 static String _escapeChar(char) { | 1420 static bool _isUnreservedChar(int char) { |
floitsch
2015/11/07 02:56:27
Move that function closer to the table.
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
| |
1421 return char < 127 && | |
1422 ((_unreservedTable[char >> 4] & (1 << (char & 0x0f))) != 0); | |
1423 } | |
1424 | |
1425 static String _escapeChar(int char) { | |
1359 assert(char <= 0x10ffff); // It's a valid unicode code point. | 1426 assert(char <= 0x10ffff); // It's a valid unicode code point. |
1360 const hexDigits = "0123456789ABCDEF"; | |
1361 List codeUnits; | 1427 List codeUnits; |
1362 if (char < 0x80) { | 1428 if (char < 0x80) { |
1363 // ASCII, a single percent encoded sequence. | 1429 // ASCII, a single percent encoded sequence. |
1364 codeUnits = new List(3); | 1430 codeUnits = new List(3); |
1365 codeUnits[0] = _PERCENT; | 1431 codeUnits[0] = _PERCENT; |
1366 codeUnits[1] = hexDigits.codeUnitAt(char >> 4); | 1432 codeUnits[1] = _hexDigits.codeUnitAt(char >> 4); |
1367 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf); | 1433 codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf); |
1368 } else { | 1434 } else { |
1369 // Do UTF-8 encoding of character, then percent encode bytes. | 1435 // Do UTF-8 encoding of character, then percent encode bytes. |
1370 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8. | 1436 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8. |
1371 int encodedBytes = 2; | 1437 int encodedBytes = 2; |
1372 if (char > 0x7ff) { | 1438 if (char > 0x7ff) { |
1373 flag = 0xe0; | 1439 flag = 0xe0; |
1374 encodedBytes = 3; | 1440 encodedBytes = 3; |
1375 if (char > 0xffff) { | 1441 if (char > 0xffff) { |
1376 encodedBytes = 4; | 1442 encodedBytes = 4; |
1377 flag = 0xf0; | 1443 flag = 0xf0; |
1378 } | 1444 } |
1379 } | 1445 } |
1380 codeUnits = new List(3 * encodedBytes); | 1446 codeUnits = new List(3 * encodedBytes); |
1381 int index = 0; | 1447 int index = 0; |
1382 while (--encodedBytes >= 0) { | 1448 while (--encodedBytes >= 0) { |
1383 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag; | 1449 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag; |
1384 codeUnits[index] = _PERCENT; | 1450 codeUnits[index] = _PERCENT; |
1385 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4); | 1451 codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4); |
1386 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf); | 1452 codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf); |
1387 index += 3; | 1453 index += 3; |
1388 flag = 0x80; // Following bytes have only high bit set. | 1454 flag = 0x80; // Following bytes have only high bit set. |
1389 } | 1455 } |
1390 } | 1456 } |
1391 return new String.fromCharCodes(codeUnits); | 1457 return new String.fromCharCodes(codeUnits); |
1392 } | 1458 } |
1393 | 1459 |
1394 /** | 1460 /** |
1395 * Runs through component checking that each character is valid and | 1461 * Runs through component checking that each character is valid and |
1396 * normalize percent escapes. | 1462 * normalize percent escapes. |
(...skipping 482 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1879 ss.write(_userInfo); | 1945 ss.write(_userInfo); |
1880 ss.write("@"); | 1946 ss.write("@"); |
1881 } | 1947 } |
1882 if (_host != null) ss.write(_host); | 1948 if (_host != null) ss.write(_host); |
1883 if (_port != null) { | 1949 if (_port != null) { |
1884 ss.write(":"); | 1950 ss.write(":"); |
1885 ss.write(_port); | 1951 ss.write(_port); |
1886 } | 1952 } |
1887 } | 1953 } |
1888 | 1954 |
1955 /** | |
1956 * Access the structure of a `data:` URI. | |
1957 * | |
1958 * Returns a [UriData] object for `data:` URIs and `null` for all other | |
1959 * URIs. | |
1960 * The [UriData] object can be used to access the media type and data | |
1961 * of a `data:` URI. | |
1962 */ | |
1963 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null; | |
1964 | |
1889 String toString() { | 1965 String toString() { |
1890 StringBuffer sb = new StringBuffer(); | 1966 StringBuffer sb = new StringBuffer(); |
1891 _addIfNonEmpty(sb, scheme, scheme, ':'); | 1967 _addIfNonEmpty(sb, scheme, scheme, ':'); |
1892 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { | 1968 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { |
1893 // File URIS always have the authority, even if it is empty. | 1969 // File URIS always have the authority, even if it is empty. |
1894 // The empty URI means "localhost". | 1970 // The empty URI means "localhost". |
1895 sb.write("//"); | 1971 sb.write("//"); |
1896 _writeAuthority(sb); | 1972 _writeAuthority(sb); |
1897 } | 1973 } |
1898 sb.write(path); | 1974 sb.write(path); |
1899 if (_query != null) { sb..write("?")..write(_query); } | 1975 if (_query != null) { sb..write("?")..write(_query); } |
1900 if (_fragment != null) { sb..write("#")..write(_fragment); } | 1976 if (_fragment != null) { sb..write("#")..write(_fragment); } |
1901 return sb.toString(); | 1977 return sb.toString(); |
1902 } | 1978 } |
1903 | 1979 |
1904 bool operator==(other) { | 1980 bool operator==(other) { |
1905 if (other is! Uri) return false; | 1981 if (other is! Uri) return false; |
1906 Uri uri = other; | 1982 Uri uri = other; |
1907 return scheme == uri.scheme && | 1983 return scheme == uri.scheme && |
1908 hasAuthority == uri.hasAuthority && | 1984 hasAuthority == uri.hasAuthority && |
1909 userInfo == uri.userInfo && | 1985 userInfo == uri.userInfo && |
1910 host == uri.host && | 1986 host == uri.host && |
1911 port == uri.port && | 1987 port == uri.port && |
1912 path == uri.path && | 1988 path == uri.path && |
1913 hasQuery == uri.hasQuery && | 1989 hasQuery == uri.hasQuery && |
1914 query == uri.query && | 1990 query == uri.query && |
1915 hasFragment == uri.hasFragment && | 1991 hasFragment == uri.hasFragment && |
1916 fragment == uri.fragment; | 1992 fragment == uri.fragment; |
1917 } | 1993 } |
1918 | 1994 |
1919 int get hashCode { | 1995 int get hashCode { |
1920 int combine(part, current) { | 1996 int combine(part, current) { |
1921 // The sum is truncated to 30 bits to make sure it fits into a Smi. | 1997 // The sum is truncated to 30 bits to make sure it fits into a Smi. |
1922 return (current * 31 + part.hashCode) & 0x3FFFFFFF; | 1998 return (current * 31 + part.hashCode) & 0x3FFFFFFF; |
1923 } | 1999 } |
1924 return combine(scheme, combine(userInfo, combine(host, combine(port, | 2000 return combine(scheme, combine(userInfo, combine(host, combine(port, |
1925 combine(path, combine(query, combine(fragment, 1))))))); | 2001 combine(path, combine(query, combine(fragment, 1))))))); |
1926 } | 2002 } |
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2245 static const int _UPPER_CASE_F = 0x46; | 2321 static const int _UPPER_CASE_F = 0x46; |
2246 static const int _UPPER_CASE_Z = 0x5A; | 2322 static const int _UPPER_CASE_Z = 0x5A; |
2247 static const int _LEFT_BRACKET = 0x5B; | 2323 static const int _LEFT_BRACKET = 0x5B; |
2248 static const int _BACKSLASH = 0x5C; | 2324 static const int _BACKSLASH = 0x5C; |
2249 static const int _RIGHT_BRACKET = 0x5D; | 2325 static const int _RIGHT_BRACKET = 0x5D; |
2250 static const int _LOWER_CASE_A = 0x61; | 2326 static const int _LOWER_CASE_A = 0x61; |
2251 static const int _LOWER_CASE_F = 0x66; | 2327 static const int _LOWER_CASE_F = 0x66; |
2252 static const int _LOWER_CASE_Z = 0x7A; | 2328 static const int _LOWER_CASE_Z = 0x7A; |
2253 static const int _BAR = 0x7C; | 2329 static const int _BAR = 0x7C; |
2254 | 2330 |
2331 static const String _hexDigits = "0123456789ABCDEF"; | |
2332 | |
2255 external static String _uriEncode(List<int> canonicalTable, | 2333 external static String _uriEncode(List<int> canonicalTable, |
2256 String text, | 2334 String text, |
2257 Encoding encoding, | 2335 Encoding encoding, |
2258 bool spaceToPlus); | 2336 bool spaceToPlus); |
2259 | 2337 |
2260 /** | 2338 /** |
2261 * Convert a byte (2 character hex sequence) in string [s] starting | 2339 * Convert a byte (2 character hex sequence) in string [s] starting |
2262 * at position [pos] to its ordinal value | 2340 * at position [pos] to its ordinal value |
2263 */ | 2341 */ |
2264 static int _hexCharPairToByte(String s, int pos) { | 2342 static int _hexCharPairToByte(String s, int pos) { |
(...skipping 22 matching lines...) Expand all Loading... | |
2287 * | 2365 * |
2288 * This function is similar to the JavaScript-function `decodeURI`. | 2366 * This function is similar to the JavaScript-function `decodeURI`. |
2289 * | 2367 * |
2290 * If [plusToSpace] is `true`, plus characters will be converted to spaces. | 2368 * If [plusToSpace] is `true`, plus characters will be converted to spaces. |
2291 * | 2369 * |
2292 * The decoder will create a byte-list of the percent-encoded parts, and then | 2370 * The decoder will create a byte-list of the percent-encoded parts, and then |
2293 * decode the byte-list using [encoding]. The default encodingis UTF-8. | 2371 * decode the byte-list using [encoding]. The default encodingis UTF-8. |
2294 */ | 2372 */ |
2295 static String _uriDecode(String text, | 2373 static String _uriDecode(String text, |
2296 {bool plusToSpace: false, | 2374 {bool plusToSpace: false, |
2297 Encoding encoding: UTF8}) { | 2375 Encoding encoding: UTF8, |
2376 int start: 0, | |
2377 int end}) { | |
2378 if (end == null) end = text.length; | |
floitsch
2015/11/07 02:56:27
Nit: end ??= text.length;
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Removed completely.
| |
2298 // First check whether there is any characters which need special handling. | 2379 // First check whether there is any characters which need special handling. |
2299 bool simple = true; | 2380 bool simple = true; |
2300 for (int i = 0; i < text.length && simple; i++) { | 2381 for (int i = start; i < end && simple; i++) { |
2301 var codeUnit = text.codeUnitAt(i); | 2382 var codeUnit = text.codeUnitAt(i); |
2302 simple = codeUnit != _PERCENT && codeUnit != _PLUS; | 2383 simple = codeUnit != _PERCENT && codeUnit != _PLUS; |
2303 } | 2384 } |
2304 List<int> bytes; | 2385 List<int> bytes; |
2305 if (simple) { | 2386 if (simple) { |
2306 if (encoding == UTF8 || encoding == LATIN1) { | 2387 if (encoding == UTF8 || encoding == LATIN1) { |
2307 return text; | 2388 return text.substring(start, end); |
2308 } else { | 2389 } else { |
2309 bytes = text.codeUnits; | 2390 bytes = text.substring(start, end).codeUnits; |
2310 } | 2391 } |
2311 } else { | 2392 } else { |
2312 bytes = new List(); | 2393 bytes = new List(); |
2313 for (int i = 0; i < text.length; i++) { | 2394 for (int i = start; i < end; i++) { |
2314 var codeUnit = text.codeUnitAt(i); | 2395 var codeUnit = text.codeUnitAt(i); |
2315 if (codeUnit > 127) { | 2396 if (codeUnit > 127) { |
2316 throw new ArgumentError("Illegal percent encoding in URI"); | 2397 throw new ArgumentError("Illegal percent encoding in URI"); |
2317 } | 2398 } |
2318 if (codeUnit == _PERCENT) { | 2399 if (codeUnit == _PERCENT) { |
2319 if (i + 3 > text.length) { | 2400 if (i + 3 > text.length) { |
2320 throw new ArgumentError('Truncated URI'); | 2401 throw new ArgumentError('Truncated URI'); |
2321 } | 2402 } |
2322 bytes.add(_hexCharPairToByte(text, i + 1)); | 2403 bytes.add(_hexCharPairToByte(text, i + 1)); |
2323 i += 2; | 2404 i += 2; |
2324 } else if (plusToSpace && codeUnit == _PLUS) { | 2405 } else if (plusToSpace && codeUnit == _PLUS) { |
2325 bytes.add(_SPACE); | 2406 bytes.add(_SPACE); |
2326 } else { | 2407 } else { |
2327 bytes.add(codeUnit); | 2408 bytes.add(codeUnit); |
2328 } | 2409 } |
2329 } | 2410 } |
2330 } | 2411 } |
2331 return encoding.decode(bytes); | 2412 return encoding.decode(bytes); |
2332 } | 2413 } |
2333 | 2414 |
2334 static bool _isAlphabeticCharacter(int codeUnit) | 2415 static bool _isAlphabeticCharacter(int codeUnit) |
2335 => (codeUnit >= _LOWER_CASE_A && codeUnit <= _LOWER_CASE_Z) || | 2416 => (codeUnit >= _LOWER_CASE_A && codeUnit <= _LOWER_CASE_Z) || |
2336 (codeUnit >= _UPPER_CASE_A && codeUnit <= _UPPER_CASE_Z); | 2417 (codeUnit >= _UPPER_CASE_A && codeUnit <= _UPPER_CASE_Z); |
2337 | 2418 |
2338 // Tables of char-codes organized as a bit vector of 128 bits where | 2419 // Tables of char-codes organized as a bit vector of 128 bits where |
2339 // each bit indicate whether a character code on the 0-127 needs to | 2420 // each bit indicate whether a character code on the 0-127 needs to |
2340 // be escaped or not. | 2421 // be escaped or not. |
2341 | 2422 |
2342 // The unreserved characters of RFC 3986. | 2423 // The unreserved characters of RFC 3986. |
2343 static const _unreservedTable = const [ | 2424 static const _unreservedTable = const [ |
2344 // LSB MSB | 2425 // LSB MSB |
2345 // | | | 2426 // | | |
2346 0x0000, // 0x00 - 0x0f 0000000000000000 | 2427 0x0000, // 0x00 - 0x0f 0000000000000000 |
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2575 // 0123456789:; = ? | 2656 // 0123456789:; = ? |
2576 0xafff, // 0x30 - 0x3f 1111111111110101 | 2657 0xafff, // 0x30 - 0x3f 1111111111110101 |
2577 // @ABCDEFGHIJKLMNO | 2658 // @ABCDEFGHIJKLMNO |
2578 0xffff, // 0x40 - 0x4f 1111111111111111 | 2659 0xffff, // 0x40 - 0x4f 1111111111111111 |
2579 // PQRSTUVWXYZ _ | 2660 // PQRSTUVWXYZ _ |
2580 0x87ff, // 0x50 - 0x5f 1111111111100001 | 2661 0x87ff, // 0x50 - 0x5f 1111111111100001 |
2581 // abcdefghijklmno | 2662 // abcdefghijklmno |
2582 0xfffe, // 0x60 - 0x6f 0111111111111111 | 2663 0xfffe, // 0x60 - 0x6f 0111111111111111 |
2583 // pqrstuvwxyz ~ | 2664 // pqrstuvwxyz ~ |
2584 0x47ff]; // 0x70 - 0x7f 1111111111100010 | 2665 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
2666 | |
2585 } | 2667 } |
2668 | |
2669 // -------------------------------------------------------------------- | |
2670 // Data URI | |
2671 // -------------------------------------------------------------------- | |
2672 | |
2673 /** | |
2674 * A way to access the structure of a `data:` URI. | |
2675 * | |
2676 * Data URIs are non-hierarchical URIs that can contain any binary data. | |
2677 * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397). | |
2678 * | |
2679 * This class allows parsing the URI text and extracting individual parts of the | |
2680 * URI, as well as building the URI text from structured parts. | |
2681 */ | |
2682 class UriData { | |
2683 static const int _noScheme = -1; | |
2684 /** | |
2685 * Contains the text content of a `data:` URI, with or without a | |
2686 * leading `data:`. | |
2687 * | |
2688 * If [_separatorIndices] starts with `4` (the index of the `:`), then | |
2689 * there is a leading `data:`, otherwise [_separatorIndices] starts with | |
2690 * `-1`. | |
2691 */ | |
2692 final String _text; | |
2693 | |
2694 /** | |
2695 * List of the separators (';', '=' and ',') in the text. | |
2696 * | |
2697 * Starts with the index of the `:` in `data:` of the mimeType. | |
2698 * That is always either -1 or 4, depending on whether `_text` includes the | |
2699 * `data:` scheme or not. | |
2700 * | |
2701 * The first speparator ends the mime type. We don't bother with finding | |
2702 * the '/' inside the mime type. | |
2703 * | |
2704 * Each two separators after that marks a parameter key and value. | |
2705 * | |
2706 * If there is a single separator left, it ends the "base64" marker. | |
2707 * | |
2708 * So the following separators are found for a text: | |
2709 * | |
2710 * data:text/plain;foo=bar;base64,ARGLEBARGLE= | |
2711 * ^ ^ ^ ^ ^ | |
2712 * | |
2713 */ | |
2714 final List<int> _separatorIndices; | |
2715 | |
2716 /** | |
2717 * Cache of the result returned by [uri]. | |
2718 */ | |
2719 Uri _uriCache; | |
2720 | |
2721 UriData._(this._text, this._separatorIndices, this._uriCache); | |
2722 | |
2723 /** | |
2724 * Creates a `data:` URI containing the [content] string. | |
2725 * | |
2726 * Equivalent to `new Uri.dataFromString(...).data`, but may | |
2727 * be more efficient if the [uri] itself isn't used. | |
2728 */ | |
2729 factory UriData.fromString(String content, | |
2730 {String mimeType, | |
2731 Encoding encoding, | |
2732 Map<String, String> parameters, | |
2733 bool base64: false}) { | |
2734 StringBuffer buffer = new StringBuffer(); | |
2735 List indices = [_noScheme]; | |
2736 String charsetName; | |
2737 String encodingName; | |
2738 if (parameters != null) charsetName = parameters["charset"]; | |
2739 if (encoding == null) { | |
2740 if (charsetName != null) { | |
2741 encoding = Encoding.getByName(charsetName); | |
2742 } | |
2743 } else if (charsetName == null) { | |
2744 // Non-null only if parameters does not contain "charset". | |
2745 encodingName = encoding.name; | |
2746 } | |
2747 encoding ??= ASCII; | |
2748 _writeUri(mimeType, encodingName, parameters, buffer, indices); | |
2749 indices.add(buffer.length); | |
2750 if (base64) { | |
2751 buffer.write(';base64,'); | |
2752 indices.add(buffer.length - 1); | |
2753 buffer.write(encoding.fuse(BASE64).encode(content)); | |
2754 } else { | |
2755 buffer.write(','); | |
2756 _uriEncodeBytes(_uricTable, encoding.encode(content), buffer); | |
2757 } | |
2758 return new UriData._(buffer.toString(), indices, null); | |
2759 } | |
2760 | |
2761 /** | |
2762 * Creates a `data:` URI containing an encoding of [bytes]. | |
2763 * | |
2764 * Equivalent to `new Uri.dataFromBytes(...).data`, but may | |
2765 * be more efficient if the [uri] itself isn't used. | |
2766 */ | |
2767 factory UriData.fromBytes(List<int> bytes, | |
2768 {mimeType: "application/octet-stream", | |
2769 Map<String, String> parameters, | |
2770 percentEncoded: false}) { | |
2771 StringBuffer buffer = new StringBuffer(); | |
2772 List indices = [_noScheme]; | |
2773 _writeUri(mimeType, null, parameters, buffer, indices); | |
2774 indices.add(buffer.length); | |
2775 if (percentEncoded) { | |
2776 buffer.write(','); | |
2777 _uriEncodeBytes(_uricTable, bytes, buffer); | |
2778 } else { | |
2779 buffer.write(';base64,'); | |
2780 indices.add(buffer.length - 1); | |
2781 BASE64.encoder | |
2782 .startChunkedConversion( | |
2783 new StringConversionSink.fromStringSink(buffer)) | |
2784 .addSlice(bytes, 0, bytes.length, true); | |
2785 } | |
2786 | |
2787 return new UriData._(buffer.toString(), indices, null); | |
2788 } | |
2789 | |
2790 /** | |
2791 * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme]. | |
2792 * | |
2793 * The [uri] must have scheme `data` and no authority or fragment, | |
2794 * and the path (concatenated with the query, if there is one) must be valid | |
2795 * as data URI content with the same rules as [parse]. | |
2796 */ | |
2797 factory UriData.fromUri(Uri uri) { | |
2798 if (uri.scheme != "data") { | |
2799 throw new ArgumentError.value(uri, "uri", | |
2800 "Scheme must be 'data'"); | |
2801 } | |
2802 if (uri.hasAuthority) { | |
2803 throw new ArgumentError.value(uri, "uri", | |
2804 "Data uri must not have authority"); | |
2805 } | |
2806 if (uri.hasFragment) { | |
2807 throw new ArgumentError.value(uri, "uri", | |
2808 "Data uri must not have a fragment part"); | |
2809 } | |
2810 if (!uri.hasQuery) { | |
2811 return _parse(uri.path, 0, uri); | |
2812 } | |
2813 // Includes path and query (and leading "data:"). | |
2814 return _parse("$uri", 5, uri); | |
2815 } | |
2816 | |
2817 /** | |
2818 * Writes the initial part of a `data:` uri, from after the "data:" | |
2819 * until just before the ',' before the data, or before a `;base64,` | |
2820 * marker. | |
2821 * | |
2822 * Of an [indices] list is passed, separator indices are stored in that | |
2823 * list. | |
2824 */ | |
2825 static void _writeUri(String mimeType, | |
2826 String charsetName, | |
2827 Map<String, String> parameters, | |
2828 StringBuffer buffer, List indices) { | |
2829 if (mimeType == null || mimeType == "text/plain") { | |
2830 mimeType = ""; | |
2831 } | |
2832 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) { | |
2833 buffer.write(mimeType); // Common cases need no escaping. | |
2834 } else { | |
2835 int slashIndex = _validateMimeType(mimeType); | |
2836 if (slashIndex < 0) { | |
2837 throw new ArgumentError.value(mimeType, "mimeType", | |
2838 "Invalid MIME type"); | |
2839 } | |
2840 buffer.write(Uri._uriEncode(_tokenCharTable, | |
2841 mimeType.substring(0, slashIndex), | |
2842 UTF8, false)); | |
2843 buffer.write("/"); | |
2844 buffer.write(Uri._uriEncode(_tokenCharTable, | |
2845 mimeType.substring(slashIndex + 1), | |
2846 UTF8, false)); | |
2847 } | |
2848 if (charsetName != null) { | |
2849 if (indices != null) { | |
2850 indices..add(buffer.length) | |
2851 ..add(buffer.length + 8); | |
2852 } | |
2853 buffer.write(";charset="); | |
2854 buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false)); | |
2855 } | |
2856 parameters?.forEach((var key, var value) { | |
2857 if (key.isEmpty) { | |
2858 throw new ArgumentError.value("", "Parameter names must not be empty"); | |
2859 } | |
2860 if (value.isEmpty) { | |
2861 throw new ArgumentError.value("", "Parameter values must not be empty", | |
2862 'parameters["$key"]'); | |
2863 } | |
2864 if (indices != null) indices.add(buffer.length); | |
2865 buffer.write(';'); | |
2866 // Encode any non-RFC2045-token character and both '%' and '#'. | |
2867 buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false)); | |
2868 if (indices != null) indices.add(buffer.length); | |
2869 buffer.write('='); | |
2870 buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false)); | |
2871 }); | |
2872 } | |
2873 | |
2874 /** | |
2875 * Checks mimeType is valid-ish (`token '/' token`). | |
2876 * | |
2877 * Returns the index of the slash, or -1 if the mime type is not | |
2878 * considered valid. | |
2879 * | |
2880 * Currently only looks for slashes, all other characters will be | |
2881 * percent-encoded as UTF-8 if necessary. | |
2882 */ | |
2883 static int _validateMimeType(String mimeType) { | |
2884 int slashIndex = -1; | |
2885 for (int i = 0; i < mimeType.length; i++) { | |
2886 var char = mimeType.codeUnitAt(i); | |
2887 if (char != Uri._SLASH) continue; | |
2888 if (slashIndex < 0) { | |
2889 slashIndex = i; | |
2890 continue; | |
2891 } | |
2892 return -1; | |
2893 } | |
2894 return slashIndex; | |
2895 } | |
2896 | |
2897 /** | |
2898 * Parses a string as a `data` URI. | |
2899 * | |
2900 * The string must have the format: | |
2901 * | |
2902 * ``` | |
2903 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a | |
2904 * ```` | |
2905 * | |
2906 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045, | |
2907 * and `data` is a sequnce of URI-characters (RFC-2396 `uric`). | |
2908 * | |
2909 * This means that all the characters must be ASCII, but the URI may contain | |
2910 * percent-escapes for non-ASCII byte values that need an interpretation | |
2911 * to be converted to the corresponding string. | |
2912 * | |
2913 * Parsing doesn't check the validity of any part, it just checks that the | |
2914 * input has the correct structure with the correct sequence of `/`, `;`, `=` | |
2915 * and `,` delimiters. | |
2916 * | |
2917 * Accessing the individual parts may fail later if they turn out to have | |
2918 * content that can't be decoded sucessfully as a string. | |
2919 */ | |
2920 static UriData parse(String uri) { | |
2921 if (!uri.startsWith("data:")) { | |
2922 throw new FormatException("Does not start with 'data:'", uri, 0); | |
2923 } | |
2924 return _parse(uri, 5, null); | |
2925 } | |
2926 | |
2927 /** | |
2928 * The [Uri] that this `UriData` is giving access to.. | |
floitsch
2015/11/07 02:56:26
Remove ".'
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
| |
2929 * | |
2930 * Returns a `Uri` with scheme `data` and the remainder of the data URI | |
2931 * as path. | |
2932 */ | |
2933 Uri get uri { | |
2934 if (_uriCache != null) return _uriCache; | |
2935 String path = _text; | |
2936 String query = null; | |
2937 int colonIndex = _separatorIndices[0]; | |
2938 int queryIndex = _text.indexOf('?', colonIndex + 1); | |
2939 int end = null; | |
2940 if (queryIndex >= 0) { | |
2941 query = _text.substring(queryIndex + 1); | |
2942 end = queryIndex; | |
2943 } | |
2944 path = _text.substring(colonIndex + 1, end); | |
2945 // TODO(lrn): This is probably too simple. We should ensure URI | |
2946 // normalization before passing in the raw strings, maybe using | |
2947 // Uri._makePath, Uri._makeQuery. | |
2948 _uriCache = new Uri._internal("data", "", null, null, path, query, null); | |
2949 return _uriCache; | |
2950 } | |
2951 | |
2952 /** | |
2953 * The MIME type of the data URI. | |
2954 * | |
2955 * A data URI consists of a "media type" followed by data. | |
2956 * The media type starts with a MIME type and can be followed by | |
2957 * extra parameters. | |
2958 * | |
2959 * Example: | |
2960 * | |
2961 * data:text/plain;charset=utf-8,Hello%20World! | |
2962 * | |
2963 * This data URI has the media type `text/plain;charset=utf-8`, which is the | |
2964 * MIME type `text/plain` with the parameter `charset` with value `utf-8`. | |
2965 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. | |
2966 * | |
2967 * If the first part of the data URI is empty, it defaults to `text/plain`. | |
2968 */ | |
2969 String get mimeType { | |
2970 int start = _separatorIndices[0] + 1; | |
2971 int end = _separatorIndices[1]; | |
2972 if (start == end) return "text/plain"; | |
2973 return Uri._uriDecode(_text, start: start, end: end); | |
2974 } | |
2975 | |
2976 /** | |
2977 * The charset parameter of the media type. | |
2978 * | |
2979 * If the parameters of the media type contains a `charset` parameter | |
2980 * then this returns its value, otherwise it returns `US-ASCII`, | |
2981 * which is the default charset for data URIs. | |
2982 */ | |
2983 String get charset { | |
2984 int parameterStart = 1; | |
2985 int parameterEnd = _separatorIndices.length - 1; // The ',' before data. | |
2986 if (isBase64) { | |
2987 // There is a ";base64" separator, so subtract one for that as well. | |
2988 parameterEnd -= 1; | |
2989 } | |
2990 for (int i = parameterStart; i < parameterEnd; i += 2) { | |
2991 var keyStart = _separatorIndices[i] + 1; | |
2992 var keyEnd = _separatorIndices[i + 1]; | |
2993 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) { | |
2994 return Uri._uriDecode(_text, start: keyEnd + 1, | |
2995 end: _separatorIndices[i + 2]); | |
2996 } | |
2997 } | |
2998 return "US-ASCII"; | |
2999 } | |
3000 | |
3001 /** | |
3002 * Whether the data is Base64 encoded or not. | |
3003 */ | |
3004 bool get isBase64 => _separatorIndices.length.isOdd; | |
3005 | |
3006 /** | |
3007 * The content part of the data URI, as its actual representation. | |
3008 * | |
3009 * This string may contain percent escapes. | |
3010 */ | |
3011 String get contentText => _text.substring(_separatorIndices.last + 1); | |
3012 | |
3013 /** | |
3014 * The content part of the data URI as bytes. | |
3015 * | |
3016 * If the data is Base64 encoded, it will be decoded to bytes. | |
3017 * | |
3018 * If the data is not Base64 encoded, it will be decoded by unescaping | |
3019 * percent-escaped characters and returning byte values of each unescaped | |
3020 * character. The bytes will not be, e.g., UTF-8 decoded. | |
3021 */ | |
3022 List<int> contentAsBytes() { | |
3023 String text = _text; | |
3024 int start = _separatorIndices.last + 1; | |
3025 if (isBase64) { | |
3026 return BASE64.decoder.convert(text, start); | |
3027 } | |
3028 | |
3029 // Not base64, do percent-decoding and return the remaining bytes. | |
3030 // Compute result size. | |
3031 const int percent = 0x25; | |
3032 int length = text.length - start; | |
3033 for (int i = start; i < text.length; i++) { | |
3034 var codeUnit = text.codeUnitAt(i); | |
3035 if (codeUnit == percent) { | |
3036 i += 2; | |
3037 length -= 2; | |
3038 } | |
3039 } | |
3040 // Fill result array. | |
3041 Uint8List result = new Uint8List(length); | |
3042 if (length == text.length) { | |
3043 result.setRange(0, length, text.codeUnits, start); | |
3044 return result; | |
3045 } | |
3046 int index = 0; | |
3047 for (int i = start; i < text.length; i++) { | |
3048 var codeUnit = text.codeUnitAt(i); | |
3049 if (codeUnit != percent) { | |
3050 result[index++] = codeUnit; | |
3051 } else { | |
3052 if (i + 2 < text.length) { | |
3053 var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1)); | |
3054 var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2)); | |
3055 if (digit1 >= 0 && digit2 >= 0) { | |
3056 int byte = digit1 * 16 + digit2; | |
3057 result[index++] = byte; | |
3058 i += 2; | |
3059 continue; | |
3060 } | |
3061 } | |
3062 throw new FormatException("Invalid percent escape", text, i); | |
3063 } | |
3064 } | |
3065 assert(index == result.length); | |
3066 return result; | |
3067 } | |
3068 | |
3069 /** | |
3070 * Returns a string created from the content of the data URI. | |
3071 * | |
3072 * If the content is Base64 encoded, it will be decoded to bytes and then | |
3073 * decoded to a string using [encoding]. | |
3074 * If encoding is omitted, the value of a `charset` parameter is used | |
3075 * if it is recongized by [Encoding.getByName], otherwise it defaults to | |
3076 * the [ASCII] encoding, which is the default encoding for data URIs | |
3077 * that do not specify an encoding. | |
3078 * | |
3079 * If the content is not Base64 encoded, it will first have percent-escapes | |
3080 * converted to bytes and then the character codes and byte values are | |
3081 * decoded using [encoding]. | |
3082 */ | |
3083 String contentAsString({Encoding encoding}) { | |
3084 if (encoding == null) { | |
3085 var charset = this.charset; // Returns "US-ASCII" if not present. | |
3086 encoding = Encoding.getByName(charset); | |
3087 if (encoding == null) { | |
3088 throw new UnsupportedError("Unknown charset: $charset"); | |
3089 } | |
3090 } | |
3091 String text = _text; | |
3092 int start = _separatorIndices.last + 1; | |
3093 if (isBase64) { | |
3094 var converter = BASE64.decoder.fuse(encoding.decoder); | |
3095 return converter.convert(text.substring(start)); | |
3096 } | |
3097 return Uri._uriDecode(text, start: start, encoding: encoding); | |
3098 } | |
3099 | |
3100 /** | |
3101 * A map representing the parameters of the media type. | |
3102 * | |
3103 * A data URI may contain parameters between the the MIME type and the | |
3104 * data. This converts these parameters to a map from parameter name | |
3105 * to parameter value. | |
3106 * The map only contains parameters that actually occur in the URI. | |
3107 * The `charset` parameter has a default value even if it doesn't occur | |
3108 * in the URI, which is reflected by the [charset] getter. This means that | |
3109 * [charset] may return a value even if `parameters["charset"]` is `null`. | |
3110 * | |
3111 * If the values contain non-ASCII values or percent escapes, they default | |
3112 * to being decoded as UTF-8. | |
3113 */ | |
3114 Map<String, String> get parameters { | |
3115 var result = <String, String>{}; | |
3116 for (int i = 3; i < _separatorIndices.length; i += 2) { | |
3117 var start = _separatorIndices[i - 2] + 1; | |
3118 var equals = _separatorIndices[i - 1]; | |
3119 var end = _separatorIndices[i]; | |
3120 String key = Uri._uriDecode(_text, start: start, end: equals); | |
3121 String value = Uri._uriDecode(_text, start: equals + 1, end: end); | |
3122 result[key] = value; | |
3123 } | |
3124 return result; | |
3125 } | |
3126 | |
3127 static UriData _parse(String text, int start, Uri sourceUri) { | |
3128 assert(start == 0 || start == 5); | |
3129 assert((start == 5) == text.startsWith("data:")); | |
3130 | |
3131 /// Character codes. | |
3132 const int comma = 0x2c; | |
3133 const int slash = 0x2f; | |
3134 const int semicolon = 0x3b; | |
3135 const int equals = 0x3d; | |
3136 List indices = [start - 1]; | |
3137 int slashIndex = -1; | |
3138 var char; | |
3139 int i = start; | |
3140 for (; i < text.length; i++) { | |
3141 char = text.codeUnitAt(i); | |
3142 if (char == comma || char == semicolon) break; | |
3143 if (char == slash) { | |
3144 if (slashIndex < 0) { | |
3145 slashIndex = i; | |
3146 continue; | |
3147 } | |
3148 throw new FormatException("Invalid MIME type", text, i); | |
3149 } | |
3150 } | |
3151 if (slashIndex < 0 && i > start) { | |
3152 // An empty MIME type is allowed, but if non-empty it must contain | |
3153 // exactly one slash. | |
3154 throw new FormatException("Invalid MIME type", text, i); | |
3155 } | |
3156 while (char != comma) { | |
3157 // parse parameters and/or "base64". | |
floitsch
2015/11/07 02:56:27
Nit: "Parse"
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
| |
3158 indices.add(i); | |
3159 i++; | |
3160 int equalsIndex = -1; | |
3161 for (; i < text.length; i++) { | |
3162 char = text.codeUnitAt(i); | |
3163 if (char == equals) { | |
3164 if (equalsIndex < 0) equalsIndex = i; | |
3165 } else if (char == semicolon || char == comma) { | |
3166 break; | |
3167 } | |
3168 } | |
3169 if (equalsIndex >= 0) { | |
3170 indices.add(equalsIndex); | |
3171 } else { | |
3172 // Have to be final "base64". | |
3173 var lastSeparator = indices.last; | |
3174 if (char != comma || | |
3175 i != lastSeparator + 7 /* "base64,".length */ || | |
3176 !text.startsWith("base64", lastSeparator + 1)) { | |
3177 throw new FormatException("Expecting '='", text, i); | |
3178 } | |
3179 break; | |
3180 } | |
3181 } | |
3182 indices.add(i); | |
3183 return new UriData._(text, indices, sourceUri); | |
3184 } | |
3185 | |
3186 /** | |
3187 * Like [Uri._uriEncode] but takes the input as bytes, not a string. | |
3188 * | |
3189 * Encodes into [buffer] instead of creating its own buffer. | |
3190 */ | |
3191 static void _uriEncodeBytes(List<int> canonicalTable, | |
3192 List<int> bytes, | |
3193 StringSink buffer) { | |
3194 // Encode the string into bytes then generate an ASCII only string | |
3195 // by percent encoding selected bytes. | |
3196 int byteOr = 0; | |
3197 for (int i = 0; i < bytes.length; i++) { | |
3198 int byte = bytes[i]; | |
3199 byteOr |= byte; | |
3200 if (byte < 128 && | |
3201 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) { | |
3202 buffer.writeCharCode(byte); | |
3203 } else { | |
3204 buffer.writeCharCode(Uri._PERCENT); | |
3205 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4)); | |
3206 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f)); | |
3207 } | |
3208 } | |
3209 if ((byteOr & ~0xFF) != 0) { | |
3210 for (int i = 0; i < bytes.length; i++) { | |
3211 var byte = bytes[i]; | |
3212 if (byte < 0 || byte > 255) { | |
3213 throw new ArgumentError.value(byte, "non-byte value"); | |
3214 } | |
3215 } | |
3216 } | |
3217 } | |
3218 | |
3219 String toString() => | |
3220 (_separatorIndices[0] == _noScheme) ? "data:$_text" : _text; | |
3221 | |
3222 // Table of the `token` characters of RFC 2045 in a URI. | |
3223 // | |
3224 // A token is any US-ASCII character except SPACE, control characters and | |
3225 // `tspecial` characters. The `tspecial` category is: | |
3226 // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='. | |
3227 // | |
3228 // In a data URI, we also need to escape '%' and '#' characters. | |
3229 static const _tokenCharTable = const [ | |
3230 // LSB MSB | |
3231 // | | | |
3232 0x0000, // 0x00 - 0x0f 00000000 00000000 | |
3233 0x0000, // 0x10 - 0x1f 00000000 00000000 | |
3234 // ! $ &' *+ -. | |
3235 0x6cd2, // 0x20 - 0x2f 01001011 00110110 | |
3236 // 01234567 89 | |
3237 0x03ff, // 0x30 - 0x3f 11111111 11000000 | |
3238 // ABCDEFG HIJKLMNO | |
3239 0xfffe, // 0x40 - 0x4f 01111111 11111111 | |
3240 // PQRSTUVW XYZ ^_ | |
3241 0xc7ff, // 0x50 - 0x5f 11111111 11100011 | |
3242 // `abcdefg hijklmno | |
3243 0xffff, // 0x60 - 0x6f 11111111 11111111 | |
3244 // pqrstuvw xyz{|}~ | |
3245 0x7fff]; // 0x70 - 0x7f 11111111 11111110 | |
3246 | |
3247 // All non-escape RFC-2396 uric characters. | |
3248 // | |
3249 // uric = reserved | unreserved | escaped | |
3250 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," | |
3251 // unreserved = alphanum | mark | |
3252 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" | |
3253 // | |
3254 // This is the same characters as in a URI query (which is URI pchar plus '?') | |
3255 static const _uricTable = Uri._queryCharTable; | |
3256 } | |
OLD | NEW |