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