Chromium Code Reviews| 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 |