Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(439)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 1432203002: Revert "Add data-URI support class to dart:core (next to Uri)." (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sdk/lib/core/core.dart ('k') | tests/corelib/data_uri_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « sdk/lib/core/core.dart ('k') | tests/corelib/data_uri_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698