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 /** |
748 * Returns the natural base URI for the current platform. | 816 * Returns the natural base URI for the current platform. |
749 * | 817 * |
750 * When running in a browser this is the current URL (from | 818 * When running in a browser this is the current URL (from |
751 * `window.location.href`). | 819 * `window.location.href`). |
752 * | 820 * |
753 * When not running in a browser this is the file URI referencing | 821 * When not running in a browser this is the file URI referencing |
754 * the current working directory. | 822 * the current working directory. |
755 */ | 823 */ |
756 external static Uri get base; | 824 external static Uri get base; |
757 | 825 |
(...skipping 533 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1291 return result.toString(); | 1359 return result.toString(); |
1292 } | 1360 } |
1293 | 1361 |
1294 static String _makeFragment(String fragment, int start, int end) { | 1362 static String _makeFragment(String fragment, int start, int end) { |
1295 if (fragment == null) return null; | 1363 if (fragment == null) return null; |
1296 return _normalize(fragment, start, end, _queryCharTable); | 1364 return _normalize(fragment, start, end, _queryCharTable); |
1297 } | 1365 } |
1298 | 1366 |
1299 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; | 1367 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; |
1300 | 1368 |
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 /** | 1369 /** |
1315 * Performs RFC 3986 Percent-Encoding Normalization. | 1370 * Performs RFC 3986 Percent-Encoding Normalization. |
1316 * | 1371 * |
1317 * Returns a replacement string that should be replace the original escape. | 1372 * Returns a replacement string that should be replace the original escape. |
1318 * Returns null if no replacement is necessary because the escape is | 1373 * Returns null if no replacement is necessary because the escape is |
1319 * not for an unreserved character and is already non-lower-case. | 1374 * not for an unreserved character and is already non-lower-case. |
1320 * | 1375 * |
1321 * Returns "%" if the escape is invalid (not two valid hex digits following | 1376 * Returns "%" if the escape is invalid (not two valid hex digits following |
1322 * the percent sign). The calling code should replace the percent | 1377 * the percent sign). The calling code should replace the percent |
1323 * sign with "%25", but leave the following two characters unmodified. | 1378 * sign with "%25", but leave the following two characters unmodified. |
1324 * | 1379 * |
1325 * If [lowerCase] is true, a single character returned is always lower case, | 1380 * If [lowerCase] is true, a single character returned is always lower case, |
1326 */ | 1381 */ |
1327 static String _normalizeEscape(String source, int index, bool lowerCase) { | 1382 static String _normalizeEscape(String source, int index, bool lowerCase) { |
1328 assert(source.codeUnitAt(index) == _PERCENT); | 1383 assert(source.codeUnitAt(index) == _PERCENT); |
1329 if (index + 2 >= source.length) { | 1384 if (index + 2 >= source.length) { |
1330 return "%"; // Marks the escape as invalid. | 1385 return "%"; // Marks the escape as invalid. |
1331 } | 1386 } |
1332 int firstDigit = source.codeUnitAt(index + 1); | 1387 int firstDigit = source.codeUnitAt(index + 1); |
1333 int secondDigit = source.codeUnitAt(index + 2); | 1388 int secondDigit = source.codeUnitAt(index + 2); |
1334 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) { | 1389 int firstDigitValue = _parseHexDigit(firstDigit); |
| 1390 int secondDigitValue = _parseHexDigit(secondDigit); |
| 1391 if (firstDigitValue < 0 || secondDigitValue < 0) { |
1335 return "%"; // Marks the escape as invalid. | 1392 return "%"; // Marks the escape as invalid. |
1336 } | 1393 } |
1337 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit); | 1394 int value = firstDigitValue * 16 + secondDigitValue; |
1338 if (_isUnreservedChar(value)) { | 1395 if (_isUnreservedChar(value)) { |
1339 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { | 1396 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) { |
1340 value |= 0x20; | 1397 value |= 0x20; |
1341 } | 1398 } |
1342 return new String.fromCharCode(value); | 1399 return new String.fromCharCode(value); |
1343 } | 1400 } |
1344 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { | 1401 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) { |
1345 // Either digit is lower case. | 1402 // Either digit is lower case. |
1346 return source.substring(index, index + 3).toUpperCase(); | 1403 return source.substring(index, index + 3).toUpperCase(); |
1347 } | 1404 } |
1348 // Escape is retained, and is already non-lower case, so return null to | 1405 // Escape is retained, and is already non-lower case, so return null to |
1349 // represent "no replacement necessary". | 1406 // represent "no replacement necessary". |
1350 return null; | 1407 return null; |
1351 } | 1408 } |
1352 | 1409 |
1353 static bool _isUnreservedChar(int ch) { | 1410 // Converts a UTF-16 code-unit to its value as a hex digit. |
1354 return ch < 127 && | 1411 // Returns -1 for non-hex digits. |
1355 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 1412 static int _parseHexDigit(int char) { |
| 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; |
1356 } | 1420 } |
1357 | 1421 |
1358 static String _escapeChar(char) { | 1422 static String _escapeChar(int char) { |
1359 assert(char <= 0x10ffff); // It's a valid unicode code point. | 1423 assert(char <= 0x10ffff); // It's a valid unicode code point. |
1360 const hexDigits = "0123456789ABCDEF"; | |
1361 List codeUnits; | 1424 List codeUnits; |
1362 if (char < 0x80) { | 1425 if (char < 0x80) { |
1363 // ASCII, a single percent encoded sequence. | 1426 // ASCII, a single percent encoded sequence. |
1364 codeUnits = new List(3); | 1427 codeUnits = new List(3); |
1365 codeUnits[0] = _PERCENT; | 1428 codeUnits[0] = _PERCENT; |
1366 codeUnits[1] = hexDigits.codeUnitAt(char >> 4); | 1429 codeUnits[1] = _hexDigits.codeUnitAt(char >> 4); |
1367 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf); | 1430 codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf); |
1368 } else { | 1431 } else { |
1369 // Do UTF-8 encoding of character, then percent encode bytes. | 1432 // 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. | 1433 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8. |
1371 int encodedBytes = 2; | 1434 int encodedBytes = 2; |
1372 if (char > 0x7ff) { | 1435 if (char > 0x7ff) { |
1373 flag = 0xe0; | 1436 flag = 0xe0; |
1374 encodedBytes = 3; | 1437 encodedBytes = 3; |
1375 if (char > 0xffff) { | 1438 if (char > 0xffff) { |
1376 encodedBytes = 4; | 1439 encodedBytes = 4; |
1377 flag = 0xf0; | 1440 flag = 0xf0; |
1378 } | 1441 } |
1379 } | 1442 } |
1380 codeUnits = new List(3 * encodedBytes); | 1443 codeUnits = new List(3 * encodedBytes); |
1381 int index = 0; | 1444 int index = 0; |
1382 while (--encodedBytes >= 0) { | 1445 while (--encodedBytes >= 0) { |
1383 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag; | 1446 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag; |
1384 codeUnits[index] = _PERCENT; | 1447 codeUnits[index] = _PERCENT; |
1385 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4); | 1448 codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4); |
1386 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf); | 1449 codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf); |
1387 index += 3; | 1450 index += 3; |
1388 flag = 0x80; // Following bytes have only high bit set. | 1451 flag = 0x80; // Following bytes have only high bit set. |
1389 } | 1452 } |
1390 } | 1453 } |
1391 return new String.fromCharCodes(codeUnits); | 1454 return new String.fromCharCodes(codeUnits); |
1392 } | 1455 } |
1393 | 1456 |
1394 /** | 1457 /** |
1395 * Runs through component checking that each character is valid and | 1458 * Runs through component checking that each character is valid and |
1396 * normalize percent escapes. | 1459 * normalize percent escapes. |
(...skipping 482 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1879 ss.write(_userInfo); | 1942 ss.write(_userInfo); |
1880 ss.write("@"); | 1943 ss.write("@"); |
1881 } | 1944 } |
1882 if (_host != null) ss.write(_host); | 1945 if (_host != null) ss.write(_host); |
1883 if (_port != null) { | 1946 if (_port != null) { |
1884 ss.write(":"); | 1947 ss.write(":"); |
1885 ss.write(_port); | 1948 ss.write(_port); |
1886 } | 1949 } |
1887 } | 1950 } |
1888 | 1951 |
| 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 |
1889 String toString() { | 1962 String toString() { |
1890 StringBuffer sb = new StringBuffer(); | 1963 StringBuffer sb = new StringBuffer(); |
1891 _addIfNonEmpty(sb, scheme, scheme, ':'); | 1964 _addIfNonEmpty(sb, scheme, scheme, ':'); |
1892 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { | 1965 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { |
1893 // File URIS always have the authority, even if it is empty. | 1966 // File URIS always have the authority, even if it is empty. |
1894 // The empty URI means "localhost". | 1967 // The empty URI means "localhost". |
1895 sb.write("//"); | 1968 sb.write("//"); |
1896 _writeAuthority(sb); | 1969 _writeAuthority(sb); |
1897 } | 1970 } |
1898 sb.write(path); | 1971 sb.write(path); |
1899 if (_query != null) { sb..write("?")..write(_query); } | 1972 if (_query != null) { sb..write("?")..write(_query); } |
1900 if (_fragment != null) { sb..write("#")..write(_fragment); } | 1973 if (_fragment != null) { sb..write("#")..write(_fragment); } |
1901 return sb.toString(); | 1974 return sb.toString(); |
1902 } | 1975 } |
1903 | 1976 |
1904 bool operator==(other) { | 1977 bool operator==(other) { |
1905 if (other is! Uri) return false; | 1978 if (other is! Uri) return false; |
1906 Uri uri = other; | 1979 Uri uri = other; |
1907 return scheme == uri.scheme && | 1980 return scheme == uri.scheme && |
1908 hasAuthority == uri.hasAuthority && | 1981 hasAuthority == uri.hasAuthority && |
1909 userInfo == uri.userInfo && | 1982 userInfo == uri.userInfo && |
1910 host == uri.host && | 1983 host == uri.host && |
1911 port == uri.port && | 1984 port == uri.port && |
1912 path == uri.path && | 1985 path == uri.path && |
1913 hasQuery == uri.hasQuery && | 1986 hasQuery == uri.hasQuery && |
1914 query == uri.query && | 1987 query == uri.query && |
1915 hasFragment == uri.hasFragment && | 1988 hasFragment == uri.hasFragment && |
1916 fragment == uri.fragment; | 1989 fragment == uri.fragment; |
1917 } | 1990 } |
1918 | 1991 |
1919 int get hashCode { | 1992 int get hashCode { |
1920 int combine(part, current) { | 1993 int combine(part, current) { |
1921 // The sum is truncated to 30 bits to make sure it fits into a Smi. | 1994 // The sum is truncated to 30 bits to make sure it fits into a Smi. |
1922 return (current * 31 + part.hashCode) & 0x3FFFFFFF; | 1995 return (current * 31 + part.hashCode) & 0x3FFFFFFF; |
1923 } | 1996 } |
1924 return combine(scheme, combine(userInfo, combine(host, combine(port, | 1997 return combine(scheme, combine(userInfo, combine(host, combine(port, |
1925 combine(path, combine(query, combine(fragment, 1))))))); | 1998 combine(path, combine(query, combine(fragment, 1))))))); |
1926 } | 1999 } |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2002 * some of the decoded characters could be characters with are | 2075 * some of the decoded characters could be characters with are |
2003 * delimiters for a given URI componene type. Always split a URI | 2076 * delimiters for a given URI componene type. Always split a URI |
2004 * component using the delimiters for the component before decoding | 2077 * component using the delimiters for the component before decoding |
2005 * the individual parts. | 2078 * the individual parts. |
2006 * | 2079 * |
2007 * For handling the [path] and [query] components consider using | 2080 * For handling the [path] and [query] components consider using |
2008 * [pathSegments] and [queryParameters] to get the separated and | 2081 * [pathSegments] and [queryParameters] to get the separated and |
2009 * decoded component. | 2082 * decoded component. |
2010 */ | 2083 */ |
2011 static String decodeComponent(String encodedComponent) { | 2084 static String decodeComponent(String encodedComponent) { |
2012 return _uriDecode(encodedComponent); | 2085 return _uriDecode(encodedComponent, 0, encodedComponent.length, |
| 2086 UTF8, false); |
2013 } | 2087 } |
2014 | 2088 |
2015 /** | 2089 /** |
2016 * Decodes the percent-encoding in [encodedComponent], converting | 2090 * Decodes the percent-encoding in [encodedComponent], converting |
2017 * pluses to spaces. | 2091 * pluses to spaces. |
2018 * | 2092 * |
2019 * It will create a byte-list of the decoded characters, and then use | 2093 * It will create a byte-list of the decoded characters, and then use |
2020 * [encoding] to decode the byte-list to a String. The default encoding is | 2094 * [encoding] to decode the byte-list to a String. The default encoding is |
2021 * UTF-8. | 2095 * UTF-8. |
2022 */ | 2096 */ |
2023 static String decodeQueryComponent( | 2097 static String decodeQueryComponent( |
2024 String encodedComponent, | 2098 String encodedComponent, |
2025 {Encoding encoding: UTF8}) { | 2099 {Encoding encoding: UTF8}) { |
2026 return _uriDecode(encodedComponent, plusToSpace: true, encoding: encoding); | 2100 return _uriDecode(encodedComponent, 0, encodedComponent.length, |
| 2101 encoding, true); |
2027 } | 2102 } |
2028 | 2103 |
2029 /** | 2104 /** |
2030 * Encode the string [uri] using percent-encoding to make it | 2105 * Encode the string [uri] using percent-encoding to make it |
2031 * safe for literal use as a full URI. | 2106 * safe for literal use as a full URI. |
2032 * | 2107 * |
2033 * All characters except uppercase and lowercase letters, digits and | 2108 * All characters except uppercase and lowercase letters, digits and |
2034 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This | 2109 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This |
2035 * is the set of characters specified in in ECMA-262 version 5.1 for | 2110 * is the set of characters specified in in ECMA-262 version 5.1 for |
2036 * the encodeURI function . | 2111 * the encodeURI function . |
2037 */ | 2112 */ |
2038 static String encodeFull(String uri) { | 2113 static String encodeFull(String uri) { |
2039 return _uriEncode(_encodeFullTable, uri, UTF8, false); | 2114 return _uriEncode(_encodeFullTable, uri, UTF8, false); |
2040 } | 2115 } |
2041 | 2116 |
2042 /** | 2117 /** |
2043 * Decodes the percent-encoding in [uri]. | 2118 * Decodes the percent-encoding in [uri]. |
2044 * | 2119 * |
2045 * Note that decoding a full URI might change its meaning as some of | 2120 * Note that decoding a full URI might change its meaning as some of |
2046 * the decoded characters could be reserved characters. In most | 2121 * the decoded characters could be reserved characters. In most |
2047 * cases an encoded URI should be parsed into components using | 2122 * cases an encoded URI should be parsed into components using |
2048 * [Uri.parse] before decoding the separate components. | 2123 * [Uri.parse] before decoding the separate components. |
2049 */ | 2124 */ |
2050 static String decodeFull(String uri) { | 2125 static String decodeFull(String uri) { |
2051 return _uriDecode(uri); | 2126 return _uriDecode(uri, 0, uri.length, UTF8, false); |
2052 } | 2127 } |
2053 | 2128 |
2054 /** | 2129 /** |
2055 * Returns the [query] split into a map according to the rules | 2130 * Returns the [query] split into a map according to the rules |
2056 * specified for FORM post in the | 2131 * specified for FORM post in the |
2057 * [HTML 4.01 specification section 17.13.4] | 2132 * [HTML 4.01 specification section 17.13.4] |
2058 * (http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 | 2133 * (http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 |
2059 * "HTML 4.01 section 17.13.4"). Each key and value in the returned | 2134 * "HTML 4.01 section 17.13.4"). Each key and value in the returned |
2060 * map has been decoded. If the [query] | 2135 * map has been decoded. If the [query] |
2061 * is the empty string an empty map is returned. | 2136 * is the empty string an empty map is returned. |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2245 static const int _UPPER_CASE_F = 0x46; | 2320 static const int _UPPER_CASE_F = 0x46; |
2246 static const int _UPPER_CASE_Z = 0x5A; | 2321 static const int _UPPER_CASE_Z = 0x5A; |
2247 static const int _LEFT_BRACKET = 0x5B; | 2322 static const int _LEFT_BRACKET = 0x5B; |
2248 static const int _BACKSLASH = 0x5C; | 2323 static const int _BACKSLASH = 0x5C; |
2249 static const int _RIGHT_BRACKET = 0x5D; | 2324 static const int _RIGHT_BRACKET = 0x5D; |
2250 static const int _LOWER_CASE_A = 0x61; | 2325 static const int _LOWER_CASE_A = 0x61; |
2251 static const int _LOWER_CASE_F = 0x66; | 2326 static const int _LOWER_CASE_F = 0x66; |
2252 static const int _LOWER_CASE_Z = 0x7A; | 2327 static const int _LOWER_CASE_Z = 0x7A; |
2253 static const int _BAR = 0x7C; | 2328 static const int _BAR = 0x7C; |
2254 | 2329 |
| 2330 static const String _hexDigits = "0123456789ABCDEF"; |
| 2331 |
2255 external static String _uriEncode(List<int> canonicalTable, | 2332 external static String _uriEncode(List<int> canonicalTable, |
2256 String text, | 2333 String text, |
2257 Encoding encoding, | 2334 Encoding encoding, |
2258 bool spaceToPlus); | 2335 bool spaceToPlus); |
2259 | 2336 |
2260 /** | 2337 /** |
2261 * Convert a byte (2 character hex sequence) in string [s] starting | 2338 * Convert a byte (2 character hex sequence) in string [s] starting |
2262 * at position [pos] to its ordinal value | 2339 * at position [pos] to its ordinal value |
2263 */ | 2340 */ |
2264 static int _hexCharPairToByte(String s, int pos) { | 2341 static int _hexCharPairToByte(String s, int pos) { |
(...skipping 21 matching lines...) Expand all Loading... |
2286 * It unescapes the string [text] and returns the unescaped string. | 2363 * It unescapes the string [text] and returns the unescaped string. |
2287 * | 2364 * |
2288 * This function is similar to the JavaScript-function `decodeURI`. | 2365 * This function is similar to the JavaScript-function `decodeURI`. |
2289 * | 2366 * |
2290 * If [plusToSpace] is `true`, plus characters will be converted to spaces. | 2367 * If [plusToSpace] is `true`, plus characters will be converted to spaces. |
2291 * | 2368 * |
2292 * The decoder will create a byte-list of the percent-encoded parts, and then | 2369 * 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. | 2370 * decode the byte-list using [encoding]. The default encodingis UTF-8. |
2294 */ | 2371 */ |
2295 static String _uriDecode(String text, | 2372 static String _uriDecode(String text, |
2296 {bool plusToSpace: false, | 2373 int start, |
2297 Encoding encoding: UTF8}) { | 2374 int end, |
| 2375 Encoding encoding, |
| 2376 bool plusToSpace) { |
| 2377 assert(0 <= start); |
| 2378 assert(start <= end); |
| 2379 assert(end <= text.length); |
| 2380 assert(encoding != null); |
2298 // First check whether there is any characters which need special handling. | 2381 // First check whether there is any characters which need special handling. |
2299 bool simple = true; | 2382 bool simple = true; |
2300 for (int i = 0; i < text.length && simple; i++) { | 2383 for (int i = start; i < end; i++) { |
2301 var codeUnit = text.codeUnitAt(i); | 2384 var codeUnit = text.codeUnitAt(i); |
2302 simple = codeUnit != _PERCENT && codeUnit != _PLUS; | 2385 if (codeUnit > 127 || |
| 2386 codeUnit == _PERCENT || |
| 2387 (plusToSpace && codeUnit == _PLUS)) { |
| 2388 simple = false; |
| 2389 break; |
| 2390 } |
2303 } | 2391 } |
2304 List<int> bytes; | 2392 List<int> bytes; |
2305 if (simple) { | 2393 if (simple) { |
2306 if (encoding == UTF8 || encoding == LATIN1) { | 2394 if (UTF8 == encoding || LATIN1 == encoding || ASCII == encoding) { |
2307 return text; | 2395 return text.substring(start, end); |
2308 } else { | 2396 } else { |
2309 bytes = text.codeUnits; | 2397 bytes = text.substring(start, end).codeUnits; |
2310 } | 2398 } |
2311 } else { | 2399 } else { |
2312 bytes = new List(); | 2400 bytes = new List(); |
2313 for (int i = 0; i < text.length; i++) { | 2401 for (int i = start; i < end; i++) { |
2314 var codeUnit = text.codeUnitAt(i); | 2402 var codeUnit = text.codeUnitAt(i); |
2315 if (codeUnit > 127) { | 2403 if (codeUnit > 127) { |
2316 throw new ArgumentError("Illegal percent encoding in URI"); | 2404 throw new ArgumentError("Illegal percent encoding in URI"); |
2317 } | 2405 } |
2318 if (codeUnit == _PERCENT) { | 2406 if (codeUnit == _PERCENT) { |
2319 if (i + 3 > text.length) { | 2407 if (i + 3 > text.length) { |
2320 throw new ArgumentError('Truncated URI'); | 2408 throw new ArgumentError('Truncated URI'); |
2321 } | 2409 } |
2322 bytes.add(_hexCharPairToByte(text, i + 1)); | 2410 bytes.add(_hexCharPairToByte(text, i + 1)); |
2323 i += 2; | 2411 i += 2; |
2324 } else if (plusToSpace && codeUnit == _PLUS) { | 2412 } else if (plusToSpace && codeUnit == _PLUS) { |
2325 bytes.add(_SPACE); | 2413 bytes.add(_SPACE); |
2326 } else { | 2414 } else { |
2327 bytes.add(codeUnit); | 2415 bytes.add(codeUnit); |
2328 } | 2416 } |
2329 } | 2417 } |
2330 } | 2418 } |
2331 return encoding.decode(bytes); | 2419 return encoding.decode(bytes); |
2332 } | 2420 } |
2333 | 2421 |
2334 static bool _isAlphabeticCharacter(int codeUnit) | 2422 static bool _isAlphabeticCharacter(int codeUnit) { |
2335 => (codeUnit >= _LOWER_CASE_A && codeUnit <= _LOWER_CASE_Z) || | 2423 var lowerCase = codeUnit | 0x20; |
2336 (codeUnit >= _UPPER_CASE_A && codeUnit <= _UPPER_CASE_Z); | 2424 return (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_Z); |
| 2425 } |
| 2426 |
| 2427 static bool _isUnreservedChar(int char) { |
| 2428 return char < 127 && |
| 2429 ((_unreservedTable[char >> 4] & (1 << (char & 0x0f))) != 0); |
| 2430 } |
2337 | 2431 |
2338 // Tables of char-codes organized as a bit vector of 128 bits where | 2432 // 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 | 2433 // each bit indicate whether a character code on the 0-127 needs to |
2340 // be escaped or not. | 2434 // be escaped or not. |
2341 | 2435 |
2342 // The unreserved characters of RFC 3986. | 2436 // The unreserved characters of RFC 3986. |
2343 static const _unreservedTable = const [ | 2437 static const _unreservedTable = const [ |
2344 // LSB MSB | 2438 // LSB MSB |
2345 // | | | 2439 // | | |
2346 0x0000, // 0x00 - 0x0f 0000000000000000 | 2440 0x0000, // 0x00 - 0x0f 0000000000000000 |
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2575 // 0123456789:; = ? | 2669 // 0123456789:; = ? |
2576 0xafff, // 0x30 - 0x3f 1111111111110101 | 2670 0xafff, // 0x30 - 0x3f 1111111111110101 |
2577 // @ABCDEFGHIJKLMNO | 2671 // @ABCDEFGHIJKLMNO |
2578 0xffff, // 0x40 - 0x4f 1111111111111111 | 2672 0xffff, // 0x40 - 0x4f 1111111111111111 |
2579 // PQRSTUVWXYZ _ | 2673 // PQRSTUVWXYZ _ |
2580 0x87ff, // 0x50 - 0x5f 1111111111100001 | 2674 0x87ff, // 0x50 - 0x5f 1111111111100001 |
2581 // abcdefghijklmno | 2675 // abcdefghijklmno |
2582 0xfffe, // 0x60 - 0x6f 0111111111111111 | 2676 0xfffe, // 0x60 - 0x6f 0111111111111111 |
2583 // pqrstuvwxyz ~ | 2677 // pqrstuvwxyz ~ |
2584 0x47ff]; // 0x70 - 0x7f 1111111111100010 | 2678 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
| 2679 |
2585 } | 2680 } |
| 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 |