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 |