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

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

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

Powered by Google App Engine
This is Rietveld 408576698