| Index: lib/src/data_uri.dart
|
| diff --git a/lib/src/data_uri.dart b/lib/src/data_uri.dart
|
| deleted file mode 100644
|
| index b8cdee95f8022c7363104fd574bc10ce772725d2..0000000000000000000000000000000000000000
|
| --- a/lib/src/data_uri.dart
|
| +++ /dev/null
|
| @@ -1,328 +0,0 @@
|
| -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -import 'dart:convert';
|
| -
|
| -import 'package:convert/convert.dart';
|
| -import 'package:crypto/crypto.dart';
|
| -import 'package:string_scanner/string_scanner.dart';
|
| -
|
| -import 'media_type.dart';
|
| -import 'scan.dart';
|
| -import 'utils.dart';
|
| -
|
| -/// Like [whitespace] from scan.dart, except that it matches URI-encoded
|
| -/// whitespace rather than literal characters.
|
| -final _whitespace = new RegExp(r'(?:(?:%0D%0A)?(?:%20|%09)+)*');
|
| -
|
| -/// A converter for percent encoding strings using UTF-8.
|
| -final _utf8Percent = UTF8.fuse(percent);
|
| -
|
| -/// A class representing a `data:` URI that provides access to its [mediaType]
|
| -/// and the [data] it contains.
|
| -///
|
| -/// Data can be encoded as a `data:` URI using [encode] or [encodeString], and
|
| -/// decoded using [decode].
|
| -///
|
| -/// This implementation is based on [RFC 2397][rfc], but as that RFC is
|
| -/// [notoriously ambiguous][ambiguities], some judgment calls have been made.
|
| -/// This class tries to match browsers' data URI logic, to ensure that it can
|
| -/// losslessly parse its own output, and to accept as much input as it can make
|
| -/// sense of. A balance has been struck between these goals so that while none
|
| -/// of them have been accomplished perfectly, all of them are close enough for
|
| -/// practical use.
|
| -///
|
| -/// [rfc]: http://tools.ietf.org/html/rfc2397
|
| -/// [ambiguities]: https://simonsapin.github.io/data-urls/
|
| -///
|
| -/// Some particular notes on the behavior:
|
| -///
|
| -/// * When encoding, all characters that are not [reserved][] in the type,
|
| -/// subtype, parameter names, and parameter values of media types are
|
| -/// percent-encoded using UTF-8.
|
| -///
|
| -/// * When decoding, the type, subtype, parameter names, and parameter values of
|
| -/// media types are percent-decoded using UTF-8. Parameter values are allowed
|
| -/// to contain non-token characters once decoded, but the other tokens are
|
| -/// not.
|
| -///
|
| -/// * As per the spec, quoted-string parameters are not supported when decoding.
|
| -///
|
| -/// * Query components are included in the decoding algorithm, but fragments are
|
| -/// not.
|
| -///
|
| -/// * Invalid media types and parameters will raise exceptions when decoding.
|
| -/// This is standard for Dart parsers but contrary to browser behavior.
|
| -///
|
| -/// * The URL and filename-safe base64 alphabet is accepted when decoding but
|
| -/// never emitted when encoding, since browsers don't support it.
|
| -///
|
| -/// [lws]: https://tools.ietf.org/html/rfc2616#section-2.2
|
| -/// [reserved]: https://tools.ietf.org/html/rfc3986#section-2.2
|
| -class DataUri implements Uri {
|
| - /// The inner URI to which all [Uri] methods are forwarded.
|
| - final Uri _inner;
|
| -
|
| - /// The byte data contained in the data URI.
|
| - final List<int> data;
|
| -
|
| - /// The media type declared for the data URI.
|
| - ///
|
| - /// This defaults to `text/plain;charset=US-ASCII`.
|
| - final MediaType mediaType;
|
| -
|
| - /// The encoding declared by the `charset` parameter in [mediaType].
|
| - ///
|
| - /// If [mediaType] has no `charset` parameter, this defaults to [ASCII]. If
|
| - /// the `charset` parameter declares an encoding that can't be found using
|
| - /// [Encoding.getByName], this returns `null`.
|
| - Encoding get declaredEncoding {
|
| - var charset = mediaType.parameters["charset"];
|
| - return charset == null ? ASCII : Encoding.getByName(charset);
|
| - }
|
| -
|
| - /// Creates a new data URI with the given [mediaType] and [data].
|
| - ///
|
| - /// If [base64] is `true` (the default), the data is base64-encoded;
|
| - /// otherwise, it's percent-encoded.
|
| - ///
|
| - /// If [encoding] is passed or [mediaType] declares a `charset` parameter,
|
| - /// [data] is encoded using that encoding. Otherwise, it's encoded using
|
| - /// [UTF8] or [ASCII] depending on whether it contains any non-ASCII
|
| - /// characters.
|
| - ///
|
| - /// Throws [ArgumentError] if [mediaType] and [encoding] disagree on the
|
| - /// encoding, and an [UnsupportedError] if [mediaType] defines an encoding
|
| - /// that's not supported by [Encoding.getByName].
|
| - factory DataUri.encodeString(String data, {bool base64: true,
|
| - MediaType mediaType, Encoding encoding}) {
|
| - if (mediaType == null) mediaType = new MediaType("text", "plain");
|
| -
|
| - var charset = mediaType.parameters["charset"];
|
| - var bytes;
|
| - if (encoding != null) {
|
| - if (charset == null) {
|
| - mediaType = mediaType.change(parameters: {"charset": encoding.name});
|
| - } else if (Encoding.getByName(charset) != encoding) {
|
| - throw new ArgumentError("Media type charset '$charset' disagrees with "
|
| - "encoding '${encoding.name}'.");
|
| - }
|
| - bytes = encoding.encode(data);
|
| - } else if (charset != null) {
|
| - encoding = Encoding.getByName(charset);
|
| - if (encoding == null) {
|
| - throw new UnsupportedError(
|
| - 'Unsupported media type charset "$charset".');
|
| - }
|
| - bytes = encoding.encode(data);
|
| - } else if (data.codeUnits.every((codeUnit) => codeUnit < 0x80)) {
|
| - // If the data is pure ASCII, don't bother explicitly defining a charset.
|
| - bytes = data.codeUnits;
|
| - } else {
|
| - // If the data isn't pure ASCII, default to UTF-8.
|
| - bytes = UTF8.encode(data);
|
| - mediaType = mediaType.change(parameters: {"charset": "utf-8"});
|
| - }
|
| -
|
| - return new DataUri.encode(bytes, base64: base64, mediaType: mediaType);
|
| - }
|
| -
|
| - /// Creates a new data URI with the given [mediaType] and [data].
|
| - ///
|
| - /// If [base64] is `true` (the default), the data is base64-encoded;
|
| - /// otherwise, it's percent-encoded.
|
| - factory DataUri.encode(List<int> data, {bool base64: true,
|
| - MediaType mediaType}) {
|
| - mediaType ??= new MediaType('text', 'plain');
|
| -
|
| - var buffer = new StringBuffer();
|
| -
|
| - // Manually stringify the media type because [section 3][rfc] requires that
|
| - // parameter values should have non-token characters URL-escaped rather than
|
| - // emitting them as quoted-strings. This also allows us to omit text/plain
|
| - // if possible.
|
| - //
|
| - // [rfc]: http://tools.ietf.org/html/rfc2397#section-3
|
| - if (mediaType.type != 'text' || mediaType.subtype != 'plain') {
|
| - buffer.write(_utf8Percent.encode(mediaType.type));
|
| - buffer.write("/");
|
| - buffer.write(_utf8Percent.encode(mediaType.subtype));
|
| - }
|
| -
|
| - mediaType.parameters.forEach((attribute, value) {
|
| - buffer.write(";${_utf8Percent.encode(attribute)}=");
|
| - buffer.write(_utf8Percent.encode(value));
|
| - });
|
| -
|
| - if (base64) {
|
| - buffer.write(";base64,");
|
| - // *Don't* use the URL-safe encoding scheme, since browsers don't actually
|
| - // support it.
|
| - buffer.write(CryptoUtils.bytesToBase64(data));
|
| - } else {
|
| - buffer.write(",");
|
| - buffer.write(percent.encode(data));
|
| - }
|
| -
|
| - return new DataUri._(data, mediaType,
|
| - new Uri(scheme: 'data', path: buffer.toString()));
|
| - }
|
| -
|
| - /// Decodes [uri] to make its [data] and [mediaType] available.
|
| - ///
|
| - /// [uri] may be a [Uri] or a [String].
|
| - ///
|
| - /// Throws an [ArgumentError] if [uri] is an invalid type or has a scheme
|
| - /// other than `data:`. Throws a [FormatException] if parsing fails.
|
| - factory DataUri.decode(uri) {
|
| - if (uri is String) {
|
| - uri = Uri.parse(uri);
|
| - } else if (uri is! Uri) {
|
| - throw new ArgumentError.value(uri, "uri", "Must be a String or a Uri.");
|
| - }
|
| -
|
| - if (uri.scheme != 'data') {
|
| - throw new ArgumentError.value(uri, "uri", "Can only decode a data: URI.");
|
| - }
|
| -
|
| - return wrapFormatException("data URI", uri.toString(), () {
|
| - // Remove the fragment, as per https://simonsapin.github.io/data-urls/.
|
| - // TODO(nweiz): Use Uri.removeFragment once sdk#24593 is fixed.
|
| - var string = uri.toString();
|
| - var fragment = string.indexOf('#');
|
| - if (fragment != -1) string = string.substring(0, fragment);
|
| - var scanner = new StringScanner(string);
|
| - scanner.expect('data:');
|
| -
|
| - // Manually scan the media type for three reasons:
|
| - //
|
| - // * Media type parameter values that aren't valid tokens are URL-encoded
|
| - // rather than quoted.
|
| - //
|
| - // * The media type may be omitted without omitting the parameters.
|
| - //
|
| - // * We need to be able to stop once we reach `;base64,`, even though at
|
| - // first it looks like a parameter.
|
| - var type;
|
| - var subtype;
|
| - var implicitType = false;
|
| - if (scanner.scan(token)) {
|
| - type = _verifyToken(scanner);
|
| - scanner.expect('/');
|
| - subtype = _expectToken(scanner);
|
| - } else {
|
| - type = 'text';
|
| - subtype = 'plain';
|
| - implicitType = true;
|
| - }
|
| -
|
| - // Scan the parameters, up through ";base64" or a comma.
|
| - var parameters = {};
|
| - var base64 = false;
|
| - while (scanner.scan(';')) {
|
| - var attribute = _expectToken(scanner);
|
| -
|
| - if (attribute != 'base64') {
|
| - scanner.expect('=');
|
| - } else if (!scanner.scan('=')) {
|
| - base64 = true;
|
| - break;
|
| - }
|
| -
|
| - // Don't use [_expectToken] because the value uses percent-encoding to
|
| - // escape non-token characters.
|
| - scanner.expect(token);
|
| - parameters[attribute] = _utf8Percent.decode(scanner.lastMatch[0]);
|
| - }
|
| - scanner.expect(',');
|
| -
|
| - if (implicitType && parameters.isEmpty) {
|
| - parameters = {"charset": "US-ASCII"};
|
| - }
|
| -
|
| - var mediaType = new MediaType(type, subtype, parameters);
|
| -
|
| - var data = base64
|
| - ? CryptoUtils.base64StringToBytes(scanner.rest)
|
| - : percent.decode(scanner.rest);
|
| -
|
| - return new DataUri._(data, mediaType, uri);
|
| - });
|
| - }
|
| -
|
| - /// Returns the percent-decoded value of the last MIME token scanned by
|
| - /// [scanner].
|
| - ///
|
| - /// Throws a [FormatException] if it's not a valid token after
|
| - /// percent-decoding.
|
| - static String _verifyToken(StringScanner scanner) {
|
| - var value = _utf8Percent.decode(scanner.lastMatch[0]);
|
| - if (!value.contains(nonToken)) return value;
|
| - scanner.error("Invalid token.");
|
| - return null;
|
| - }
|
| -
|
| - /// Scans [scanner] through a MIME token and returns its percent-decoded
|
| - /// value.
|
| - ///
|
| - /// Throws a [FormatException] if it's not a valid token after
|
| - /// percent-decoding.
|
| - static String _expectToken(StringScanner scanner) {
|
| - scanner.expect(token, name: "a token");
|
| - return _verifyToken(scanner);
|
| - }
|
| -
|
| - DataUri._(this.data, this.mediaType, this._inner);
|
| -
|
| - /// Returns the decoded [data] decoded using [encoding].
|
| - ///
|
| - /// [encoding] defaults to [declaredEncoding]. If the declared encoding isn't
|
| - /// supported by [Encoding.getByName] and [encoding] isn't passed, this throws
|
| - /// an [UnsupportedError].
|
| - String dataAsString({Encoding encoding}) {
|
| - encoding ??= declaredEncoding;
|
| - if (encoding == null) {
|
| - throw new UnsupportedError(
|
| - 'Unsupported media type charset '
|
| - '"${mediaType.parameters["charset"]}".');
|
| - }
|
| -
|
| - return encoding.decode(data);
|
| - }
|
| -
|
| - String get scheme => _inner.scheme;
|
| - String get authority => _inner.authority;
|
| - String get userInfo => _inner.userInfo;
|
| - String get host => _inner.host;
|
| - int get port => _inner.port;
|
| - String get path => _inner.path;
|
| - String get query => _inner.query;
|
| - String get fragment => _inner.fragment;
|
| - Uri replace({String scheme, String userInfo, String host, int port,
|
| - String path, Iterable<String> pathSegments, String query,
|
| - Map<String, String> queryParameters, String fragment}) =>
|
| - _inner.replace(
|
| - scheme: scheme, userInfo: userInfo, host: host, port: port,
|
| - path: path, pathSegments: pathSegments, query: query,
|
| - queryParameters: queryParameters, fragment: fragment);
|
| - Uri removeFragment() => _inner.removeFragment();
|
| - List<String> get pathSegments => _inner.pathSegments;
|
| - Map<String, String> get queryParameters => _inner.queryParameters;
|
| - Uri normalizePath() => _inner.normalizePath();
|
| - bool get isAbsolute => _inner.isAbsolute;
|
| - Uri resolve(String reference) => _inner.resolve(reference);
|
| - Uri resolveUri(Uri reference) => _inner.resolveUri(reference);
|
| - bool get hasScheme => _inner.hasScheme;
|
| - bool get hasAuthority => _inner.hasAuthority;
|
| - bool get hasPort => _inner.hasPort;
|
| - bool get hasQuery => _inner.hasQuery;
|
| - bool get hasFragment => _inner.hasFragment;
|
| - bool get hasEmptyPath => _inner.hasEmptyPath;
|
| - bool get hasAbsolutePath => _inner.hasAbsolutePath;
|
| - String get origin => _inner.origin;
|
| - String toFilePath({bool windows}) => _inner.toFilePath(windows: windows);
|
| - String toString() => _inner.toString();
|
| - bool operator==(other) => _inner == other;
|
| - int get hashCode => _inner.hashCode;
|
| -}
|
|
|