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

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