Chromium Code Reviews| Index: sdk/lib/core/uri.dart |
| diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart |
| index 78f42de6bdd52bbf322cd55c9ab71673557bbad9..89c0a78862a44749579a1b7484280494cc0539f0 100644 |
| --- a/sdk/lib/core/uri.dart |
| +++ b/sdk/lib/core/uri.dart |
| @@ -2309,23 +2309,40 @@ class Uri { |
| */ |
| static String _uriDecode(String text, |
| {bool plusToSpace: false, |
| - Encoding encoding: UTF8}) { |
| + Encoding encoding: UTF8, |
| + int start: 0, |
| + int end}) { |
| + if (end == null) end = text.length; |
|
floitsch
2015/11/07 02:56:26
if we have start and end, shouldn't we at least ch
Lasse Reichstein Nielsen
2015/11/09 10:27:38
This is a private function, I usually don't check
|
| // First check whether there is any characters which need special handling. |
| bool simple = true; |
| - for (int i = 0; i < text.length && simple; i++) { |
| + for (int i = start; i < end && simple; i++) { |
| var codeUnit = text.codeUnitAt(i); |
| simple = codeUnit != _PERCENT && codeUnit != _PLUS; |
|
floitsch
2015/11/07 02:56:26
I prefer:
if (codeUnit == _PERCENT || codeUnit ==
Lasse Reichstein Nielsen
2015/11/09 10:27:38
Done.
|
| } |
| List<int> bytes; |
| if (simple) { |
| if (encoding == UTF8 || encoding == LATIN1) { |
| - return text; |
| - } else { |
| + return text.substring(start, end); |
| + } else if (start == 0 && end == text.length) { |
| bytes = text.codeUnits; |
| + } else { |
| + var decoder = encoding.decoder; |
| + var result; |
| + var conversionSink = decoder.startChunkedConversion( |
| + new ChunkedConversionSink((list) { |
| + result = list.join(); |
| + })); |
| + if (conversionSink is ByteConversionSink) { |
| + conversionSink.addSlice(text.codeUnits, start, end, true); |
| + } else { |
| + conversionSink.add(text.codeUnits.sublist(start, end)); |
| + conversionSink.close(); |
| + } |
| + return result; |
| } |
| } else { |
| bytes = new List(); |
| - for (int i = 0; i < text.length; i++) { |
| + for (int i = start; i < end; i++) { |
| var codeUnit = text.codeUnitAt(i); |
| if (codeUnit > 127) { |
| throw new ArgumentError("Illegal percent encoding in URI"); |
| @@ -2597,4 +2614,518 @@ class Uri { |
| 0xfffe, // 0x60 - 0x6f 0111111111111111 |
| // pqrstuvwxyz ~ |
| 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
| + |
| +} |
| + |
| +// -------------------------------------------------------------------- |
| +// Data URI |
| +// -------------------------------------------------------------------- |
| + |
| +/** |
| + * A representation of a `data:` URI. |
| + * |
| + * Data URIs are non-hierarchial URIs that contain can contain any data. |
| + * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397). |
| + * |
| + * This class allows parsing the URI text and extracting individual parts of the |
| + * URI, as well as building the URI text from structured parts. |
| + */ |
| +class DataUri { |
|
nweiz
2015/10/15 21:09:03
It seems strange that this isn't a subclass of Uri
Lasse Reichstein Nielsen
2015/10/28 13:55:47
The Uri class is designed only for hierarchial URI
nweiz
2015/10/29 00:28:36
That's not communicated anywhere in the documentat
Lasse Reichstein Nielsen
2015/11/03 18:02:52
Good points.
So this class is not really a "data U
Lasse Reichstein Nielsen
2015/11/09 10:27:37
On 2015/11/03 18:02:52, Lasse Reichstein Nielsen w
|
| + static const int _noScheme = -1; |
| + /** |
| + * Contains the text content of a `data:` URI, with or without a |
| + * leading `data:`. |
| + * |
| + * If [_separatorIndices] starts with `4` (the index of the `:`), then |
| + * there is a leading `data:`, otherwise _separatorIndices starts with |
| + * `-1`. |
| + */ |
| + final String _text; |
| + |
| + /** |
| + * List of the separators (';', '=' and ',') in the text. |
| + * |
| + * Starts with the index of the index of the `:` in `data:` of the mimeType. |
| + * That is always either -1 or 4, depending on whether `_text` includes the |
| + * `data:` scheme or not. |
| + * |
| + * The first speparator ends the mime type. We don't bother with finding |
| + * the '/' inside the mime type. |
| + * |
| + * Each two separators after that marks a parameter key and value. |
| + * |
| + * If there is a single separator left, it ends the "base64" marker. |
| + * |
| + * So the following separators are found for a text: |
| + * |
| + * data:text/plain;foo=bar;base64,ARGLEBARGLE= |
| + * ^ ^ ^ ^ ^ |
| + * |
| + */ |
| + List<int> _separatorIndices; |
| + |
| + DataUri._(this._text, |
| + this._separatorIndices); |
| + |
| + /** The entire content of the data URI, including the leading `data:`. */ |
| + String get text => _separatorIndices[0] == _noScheme ? "data:$_text" : _text; |
| + |
| + /** |
| + * Creates a `data:` URI containing the contents as percent-encoded text. |
| + */ |
| + factory DataUri.fromString(String content, |
|
nweiz
2015/10/15 21:09:03
Why can't you base64-encode text, or percent-encod
Lasse Reichstein Nielsen
2015/10/16 14:38:45
You can't base-64 encode text - base64 encoding on
nweiz
2015/10/19 19:51:20
I was thinking something parallel to File.writeAsS
Lasse Reichstein Nielsen
2015/10/28 13:55:47
True. We probably should have an encoding here: Th
|
| + {mimeType: "text/plain", |
| + Iterable<DataUriParameter> parameters}) { |
|
nweiz
2015/10/15 21:09:03
Right now, the encoding is implicitly always UTF-8
Lasse Reichstein Nielsen
2015/10/16 14:38:45
Good point. The default, if nothing is written, is
Lasse Reichstein Nielsen
2015/10/28 13:55:47
I've changed this to add an "Encoding charset" par
|
| + StringBuffer buffer = new StringBuffer(); |
| + List indices = [_noScheme]; |
| + _writeUri(mimeType, parameters, buffer, indices); |
| + indices.add(buffer.length); |
| + buffer.write(','); |
| + buffer.write(Uri.encodeComponent(content)); |
|
nweiz
2015/10/15 21:09:04
URI.encodeComponent doesn't encode a number of res
Lasse Reichstein Nielsen
2015/10/16 14:38:45
ACK. The syntax for parameter keys an values are R
nweiz
2015/10/19 19:51:20
You should certainly percent-encode *here*, becaus
Lasse Reichstein Nielsen
2015/10/28 13:55:47
Ack, rereading the RFC again. And again.
It's almo
nweiz
2015/10/29 00:28:36
I think that's the right behavior. I believe there
|
| + return new DataUri._(buffer.toString(), indices); |
| + } |
| + |
| + /** |
| + * Creates a `data:` URI string containing the base-64 encoded content bytes. |
| + * |
| + * It defaults to having the mime-type `application/octet-stream`. |
| + */ |
| + factory DataUri.fromBytes(List<int> bytes, |
| + {mimeType: "application/octet-stream", |
| + Iterable<DataUriParameter> parameters}) { |
| + StringBuffer buffer = new StringBuffer(); |
| + List indices = [_noScheme]; |
| + _writeUri(mimeType, parameters, buffer, indices); |
| + indices.add(buffer.length); |
| + buffer.write(';base64,'); |
| + indices.add(buffer.length - 1); |
| + BASE64.encoder.startChunkedConversion( |
| + new StringConversionSink.fromStringSink(buffer)) |
| + .addSlice(bytes, 0, bytes.length, true); |
| + return new DataUri._(buffer.toString(), indices); |
| + } |
| + |
| + /** |
| + * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme]. |
| + * |
| + * The [uri] must have scheme `data` and no authority, query or fragment, |
| + * and the path must be valid as a data URI. |
| + */ |
| + factory DataUri.fromUri(Uri uri) { |
|
nweiz
2015/10/15 21:09:03
This should document when it will throw FormatExce
Lasse Reichstein Nielsen
2015/10/16 14:38:45
Good point.
Lasse Reichstein Nielsen
2015/10/28 13:55:47
Documented on parse, referenced here.
|
| + if (uri.scheme != "data") { |
| + throw new ArgumentError.value(uri, "uri", |
| + "Scheme must be 'data'"); |
| + } |
| + if (uri.hasAuthority) { |
| + throw new ArgumentError.value(uri, "uri", |
| + "Data uri must not have authority"); |
| + } |
| + if (uri.hasQuery) { |
| + throw new ArgumentError.value(uri, "uri", |
| + "Data uri must not have a query part"); |
| + } |
| + if (uri.hasFragment) { |
| + throw new ArgumentError.value(uri, "uri", |
| + "Data uri must not have a fragment part"); |
| + } |
|
nweiz
2015/10/15 21:09:03
According to https://simonsapin.github.io/data-url
Lasse Reichstein Nielsen
2015/10/16 14:38:45
True - '?' is a valid uric, so the query should be
Lasse Reichstein Nielsen
2015/10/28 13:55:47
True. Fixed.
|
| + return _parse(uri.path, 0); |
| + } |
| + |
| + /** |
| + * Writes the initial part of a `data:` uri, from after the "data:" |
| + * until just before the ',' before the data, or before a `;base64,` |
| + * marker. |
| + * |
| + * Of an [indices] list is passed, separator indices are stored in that |
| + * list. |
| + */ |
| + static void _writeUri(String mimeType, |
| + Iterable<DataUriParameter> parameters, |
| + StringBuffer buffer, List indices) { |
| + if (mimeType == null) { |
| + mimeType = "text/plain"; |
| + } |
| + if (mimeType.isEmpty || |
| + identical(mimeType, "text/plain") || |
|
nweiz
2015/10/15 21:09:03
Consider omitting the text/plain mime type, since
Lasse Reichstein Nielsen
2015/10/16 14:38:45
Good idea.
|
| + identical(mimeType, "application/octet-stream")) { |
| + buffer.write(mimeType); // Common cases need no escaping. |
| + } else { |
| + int slashIndex = _validateMimeType(mimeType); |
| + if (slashIndex < 0) { |
| + throw new ArgumentError.value(mimeType, "mimeType", |
| + "Invalid MIME type"); |
| + } |
| + buffer.write(Uri._uriEncode(_tokenCharTable, |
| + mimeType.substring(0, slashIndex))); |
| + buffer.write("/"); |
| + buffer.write(Uri._uriEncode(_tokenCharTable, |
| + mimeType.substring(slashIndex + 1))); |
| + } |
| + if (parameters != null) { |
| + for (var parameter in parameters) { |
| + if (indices != null) indices.add(buffer.length); |
| + buffer.write(';'); |
| + // Encode any non-RFC2045-token character as well as '%' and '#'. |
| + buffer.write(Uri._uriEncode(_tokenCharTable, parameter.key)); |
| + if (indices != null) indices.add(buffer.length); |
| + buffer.write('='); |
| + buffer.write(Uri._uriEncode(_tokenCharTable, parameter.value)); |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * Checks mimeType is valid-ish (`token '/' token`). |
| + * |
| + * Returns the index of the slash, or -1 if the mime type is not |
| + * considered valid. |
| + * |
| + * Currently only looks for slashes, all other characters will be |
| + * percent-encoded as UTF-8 if necessary. |
| + */ |
| + static int _validateMimeType(String mimeType) { |
| + int slashIndex = -1; |
| + for (int i = 0; i < mimeType.length; i++) { |
| + var char = mimeType.codeUnitAt(i); |
| + if (char != Uri._SLASH) continue; |
| + if (slashIndex < 0) { |
| + slashIndex = i; |
| + continue; |
| + } |
| + return -1; |
| + } |
| + return slashIndex; |
| + } |
| + |
| + /** |
| + * Creates a [Uri] with the content of [DataUri.fromString]. |
| + * |
| + * The resulting URI will have `data` as scheme and the remainder |
| + * of the data URI as path. |
| + * |
| + * Equivalent to creating a `DataUri` using `new DataUri.fromString` and |
| + * calling `toUri` on the result. |
| + */ |
| + static Uri uriFromString(String content, |
| + {mimeType: "text/plain", |
| + Iterable<DataUriParameter> parameters}) { |
| + var buffer = new StringBuffer(); |
| + _writeUri(mimeType, parameters, buffer, null); |
| + buffer.write(','); |
| + buffer.write(Uri.encodeComponent(content)); |
| + return new Uri(scheme: "data", path: buffer.toString()); |
| + } |
| + |
| + /** |
| + * Creates a [Uri] with the content of [DataUri.fromBytes]. |
| + * |
| + * The resulting URI will have `data` as scheme and the remainder |
| + * of the data URI as path. |
| + * |
| + * Equivalent to creating a `DataUri` using `new DataUri.fromBytes` and |
| + * calling `toUri` on the result. |
| + */ |
| + static Uri uriFromBytes(List<int> bytes, |
| + {mimeType: "text/plain", |
| + Iterable<DataUriParameter> parameters}) { |
| + var buffer = new StringBuffer(); |
| + _writeUri(mimeType, parameters, buffer, null); |
| + buffer.write(';base64,'); |
| + BASE64.encoder.startChunkedConversion(buffer) |
| + .addSlice(bytes, 0, bytes.length, true); |
| + return new Uri(scheme: "data", path: buffer.toString()); |
| + } |
| + |
| + /** |
| + * Parses a string as a `data` URI. |
| + */ |
| + static DataUri parse(String uri) { |
| + if (!uri.startsWith("data:")) { |
| + throw new FormatException("Does not start with 'data:'", uri, 0); |
| + } |
| + return _parse(uri, 5); |
| + } |
| + |
| + /** |
| + * Converts a `DataUri` to a [Uri]. |
| + * |
| + * Returns a `Uri` with scheme `data` and the remainder of the data URI |
| + * as path. |
| + */ |
| + Uri toUri() { |
| + String content = _text; |
| + int colonIndex = _separatorIndices[0]; |
| + if (colonIndex >= 0) { |
| + content = _text.substring(colonIndex + 1); |
| + } |
| + return new Uri._internal("data", null, null, null, content, null, null); |
| + } |
| + |
| + /** |
| + * The MIME type of the data URI. |
| + * |
| + * A data URI consists of a "media type" followed by data. |
| + * The mediatype starts with a MIME type and can be followed by |
| + * extra parameters. |
| + * |
| + * Example: |
| + * |
| + * data:text/plain;encoding=utf-8,Hello%20World! |
| + * |
| + * This data URI has the media type `text/plain;encoding=utf-8`, which is the |
| + * MIME type `text/plain` with the parameter `encoding` with value `utf-8`. |
| + * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. |
| + * |
| + * If the first part of the data URI is empty, it defaults to `text/plain`. |
| + */ |
| + String get mimeType { |
| + int start = _separatorIndices[0] + 1; |
| + int end = _separatorIndices[1]; |
| + if (start == end) return "text/plain"; |
| + return Uri._uriDecode(_text, start: start, end: end); |
| + } |
| + |
| + /** |
| + * Whether the data is base64 encoded or not. |
| + */ |
| + bool get isBase64 => _separatorIndices.length.isOdd; |
| + |
| + /** |
| + * The content part of the data URI, as its actual representation. |
| + * |
| + * This string may contain percent escapes. |
| + */ |
| + String get contentText => _text.substring(_separatorIndices.last + 1); |
| + |
| + /** |
| + * The content part of the data URI as bytes. |
| + * |
| + * If the data is base64 encoded, it will be decoded to bytes. |
| + * |
| + * If the data is not base64 encoded, it will be decoded by unescaping |
| + * percent-escaped characters and returning byte values of each unescaped |
| + * character. The bytes will not be, e.g., UTF-8 decoded. |
| + */ |
| + List<int> contentAsBytes() { |
| + String text = _text; |
| + int start = _separatorIndices.last + 1; |
| + if (isBase64) { |
| + if (text.endsWith("%3D")) { |
| + return BASE64.decode(Uri._uriDecode(text, start: start, |
| + encoding: LATIN1)); |
|
nweiz
2015/10/15 21:09:04
Why does this assume a LATIN1 encoding? It should
Lasse Reichstein Nielsen
2015/10/16 14:38:45
This function is not creating text, only bytes, so
nweiz
2015/10/19 19:51:20
It would be good to document that in a comment.
Lasse Reichstein Nielsen
2015/10/28 13:55:47
No longer necessary using the new BASE64 decoder
(
|
| + } |
| + return BASE64.decode(text.substring(start)); |
| + } |
| + |
| + // Not base64, do percent-decoding and return the remaining bytes. |
| + // Compute result size. |
| + const int percent = 0x25; |
| + int length = text.length - start; |
| + for (int i = start; i < text.length; i++) { |
| + var codeUnit = text.codeUnitAt(i); |
| + if (codeUnit == percent) { |
| + i += 2; |
| + length -= 2; |
| + } |
| + } |
| + // Fill result array. |
| + Uint8List result = new Uint8List(length); |
| + if (length == text.length) { |
| + result.setRange(0, length, text.codeUnits, start); |
| + return result; |
| + } |
| + int index = 0; |
| + for (int i = start; i < text.length; i++) { |
| + var codeUnit = text.codeUnitAt(i); |
| + if (codeUnit != percent) { |
| + result[index++] = codeUnit; |
| + } else { |
| + if (i + 2 < text.length) { |
| + var digit1 = _hexDigit(text.codeUnitAt(i + 1)); |
| + var digit2 = _hexDigit(text.codeUnitAt(i + 2)); |
| + if (digit1 >= 0 && digit2 >= 0) { |
| + int byte = digit1 * 16 + digit2; |
| + result[index++] = byte; |
| + i += 2; |
| + continue; |
| + } |
| + } |
| + throw new FormatException("Invalid percent escape", text, i); |
| + } |
| + } |
| + assert(index == result.length); |
| + return result; |
| + } |
| + |
| + // Converts a UTF-16 code-unit to its value as a hex digit. |
| + // Returns -1 for non-hex digits. |
| + int _hexDigit(int char) { |
| + const int char_0 = 0x30; |
| + const int char_a = 0x61; |
| + |
| + int digit = char ^ char_0; |
| + if (digit <= 9) return digit; |
| + char = ((char | 0x20) - char_a) & 0xFFFF; |
| + if (char < 6) return 10 + char; |
| + return -1; |
| + } |
| + |
| + /** |
| + * Returns a string created from the content of the data URI. |
| + * |
| + * If the content is base64 encoded, it will be decoded to bytes and then |
| + * decoded to a string using [encoding]. |
| + * |
| + * If the content is not base64 encoded, it will first have percent-escapes |
| + * converted to bytes and then the character codes and byte values are |
| + * decoded using [encoding]. |
| + */ |
| + String contentAsString({Encoding encoding: UTF8}) { |
|
nweiz
2015/10/15 21:09:03
The encoding should be taken from the URI's parame
Lasse Reichstein Nielsen
2015/10/16 14:38:45
I really, really don't want to parse the "charset"
nweiz
2015/10/19 19:51:20
I think never failing by default is less useful th
Lasse Reichstein Nielsen
2015/10/28 13:55:47
Sounds reasonable. If there is no charset paramete
|
| + String text = _text; |
| + int start = _separatorIndices.last + 1; |
| + if (isBase64) { |
| + var converter = BASE64.decoder.fuse(encoding.decoder); |
| + if (text.endsWith("%3D")) { |
| + return converter.convert(Uri._uriDecode(text, start: start, |
| + encoding: LATIN1)); |
| + } |
| + return converter.convert(text.substring(start)); |
| + } |
| + return Uri._uriDecode(text, start: start, encoding: encoding); |
| + } |
| + |
| + /** |
| + * An iterable over the parameters of the data URI. |
| + * |
| + * A data URI may contain parameters between the the MIMI type and the |
|
nweiz
2015/10/15 21:09:03
Nit: "MIMI" -> "MIME"
Lasse Reichstein Nielsen
2015/10/16 14:38:45
:)
|
| + * data. This iterates through those parameters, returning each as a |
| + * [DataUriParameter] pair of key and value. |
| + */ |
| + Iterable<DataUriParameter> get parameters sync* { |
| + for (int i = 3; i < _separatorIndices.length; i += 2) { |
| + var start = _separatorIndices[i - 2] + 1; |
| + var equals = _separatorIndices[i - 1]; |
| + var end = _separatorIndices[i]; |
|
nweiz
2015/10/15 21:09:04
It looks like this will incorrectly accept invalid
Lasse Reichstein Nielsen
2015/10/16 14:38:45
The parser should avoid that. This assumes that th
nweiz
2015/10/19 19:51:20
That might be true. It's a good thing to write tes
Lasse Reichstein Nielsen
2015/10/28 13:55:47
It's not even possible to test it, because it's al
|
| + String key = Uri._uriDecode(_text, start: start, end: equals); |
| + String value = Uri._uriDecode(_text, start: equals + 1, end: end); |
|
nweiz
2015/10/15 21:09:03
What about whitespace? If the spec is interpreted
Lasse Reichstein Nielsen
2015/10/16 14:38:45
The URI grammars don't generally allow space chara
nweiz
2015/10/19 19:51:20
Chrome accepts literal spaces in data URIs, but no
Lasse Reichstein Nielsen
2015/10/28 13:55:47
ACK, so we must escape existing percent characters
Lasse Reichstein Nielsen
2015/10/28 13:55:47
It seems Chrome distinguishes between "data:text/h
|
| + yield new DataUriParameter(key, value); |
| + } |
| + } |
| + |
| + static DataUri _parse(String text, int start) { |
| + assert(start == 0 || start == 5); |
| + assert((start == 5) == text.startsWith("data:")); |
| + |
| + /// Character codes. |
| + const int comma = 0x2c; |
| + const int slash = 0x2f; |
| + const int semicolon = 0x3b; |
| + const int equals = 0x3d; |
| + List indices = [start - 1]; |
| + int slashIndex = -1; |
| + var char; |
| + int i = start; |
| + for (; i < text.length; i++) { |
| + char = text.codeUnitAt(i); |
| + if (char == comma || char == semicolon) break; |
| + if (char == slash) { |
| + if (slashIndex < 0) { |
| + slashIndex = i; |
| + continue; |
| + } |
| + throw new FormatException("Invalid MIME type", text, i); |
| + } |
| + } |
| + if (slashIndex < 0 && i > start) { |
| + // An empty MIME type is allowed, but if non-empty it must contain |
| + // exactly one slash. |
| + throw new FormatException("Invalid MIME type", text, i); |
| + } |
| + while (char != comma) { |
| + // parse parameters and/or "base64". |
| + indices.add(i); |
| + i++; |
| + int equalsIndex = -1; |
| + for (; i < text.length; i++) { |
| + char = text.codeUnitAt(i); |
| + if (char == equals) { |
| + if (equalsIndex < 0) equalsIndex = i; |
| + } else if (char == semicolon || char == comma) { |
| + break; |
| + } |
| + } |
| + if (equalsIndex >= 0) { |
| + indices.add(equalsIndex); |
| + } else { |
| + // Have to be final "base64". |
| + var lastSeparator = indices.last; |
| + if (char != comma || |
| + i != lastSeparator + 7 /* "base64,".length */ || |
| + !text.startsWith("base64", lastSeparator + 1)) { |
| + throw new FormatException("Expecting '='", text, i); |
| + } |
| + break; |
| + } |
| + } |
| + indices.add(i); |
| + return new DataUri._(text, indices); |
| + } |
| + |
| + String toString() => text; |
| + |
| + // Table of the `token` characters of RFC 2045 in a URI. |
| + // |
| + // A token is any US-ASCII character except SPACE, control characters and |
| + // `tspecial` characters. The `tspecial` category is: |
| + // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='. |
| + // |
| + // In a data URI, we also need to escape '%' and '#' characters. |
| + static const _tokenCharTable = const [ |
| + // LSB MSB |
| + // | | |
| + 0x0000, // 0x00 - 0x0f 00000000 00000000 |
| + 0x0000, // 0x10 - 0x1f 00000000 00000000 |
| + // ! $ &' *+ -. |
| + 0x6cd2, // 0x20 - 0x2f 01001011 00110110 |
| + // 01234567 89 |
| + 0x03ff, // 0x30 - 0x3f 11111111 11000000 |
| + // ABCDEFG HIJKLMNO |
| + 0xfffe, // 0x40 - 0x4f 01111111 11111111 |
| + // PQRSTUVW XYZ ^_ |
| + 0xc7ff, // 0x50 - 0x5f 11111111 11100011 |
| + // `abcdefg hijklmno |
| + 0xffff, // 0x60 - 0x6f 11111111 11111111 |
| + // pqrstuvw xyz{|}~ |
| + 0x7fff]; // 0x70 - 0x7f 11111111 11111110 |
| +} |
| + |
| +/** |
| + * A parameter of a data URI. |
| + * |
| + * A parameter is a key and a value. |
| + * |
| + * The key and value are the actual values to be encoded into the URI. |
| + * They will be escaped if necessary when creating a data URI, |
| + * and have been unescaped when extracted from a data URI. |
| + */ |
| +class DataUriParameter { |
|
nweiz
2015/10/15 21:09:03
Why isn't this just a map? Maps are much easier to
Lasse Reichstein Nielsen
2015/10/16 14:38:45
Because the same parameter name may occur more tha
nweiz
2015/10/19 19:51:20
Can they? The MIME spec isn't explicit about this,
Lasse Reichstein Nielsen
2015/10/28 13:55:47
Good point. It doesn't actually look like paramete
|
| + /** Parameter key. */ |
| + final String key; |
| + /** Parameter value. */ |
| + final String value; |
| + DataUriParameter(this.key, this.value); |
| + |
| + /** |
| + * Creates an iterable of parameters from a map from key to value. |
| + * |
| + * Parameter keys are not required to be unique in a data URI, but |
| + * when they are, a map can be used to represent the parameters, and |
| + * this function provides a way to access the map pairs as parameter |
| + * values. |
| + */ |
| + static Iterable<DataUriParameter> fromMap(Map<String, String> headers) sync* { |
| + for (String key in headers.keys) { |
| + yield new DataUriParameter(key, headers[key]); |
| + } |
| + } |
| } |