dart-sdk/tests/corelib/data_uri_test.dart
Lasse R.H. Nielsen a3e39fadd6 Make UriData more case-insensitive.
Add `isMimeType` method to `UriData` class, to allow case-insensitive
checking of the MIME type.
Add `isCharset` and `isEncoding` methods to `UriData` class,
to allow case-insensitive and alternative-encoding-name aware checking
of the MIME type "charset" parameter.
Make `UriData.fromString` and `UriData.fromBytes` recognize and omit
a "text/plain" `mimeType` even if it is not all lower-case.

Be case-insensitive in a few cases where we weren't before
(like the `charset` getter not recognizing `CHARSET=utf-8`.)

Fixes #28592
TEST=corelib/data_uri_test.dart updated

BUG= http://dartbug.com/28592

Change-Id: Ia885af69d271856af7fadfe93851e07eff6ddca2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217366
Reviewed-by: Nate Bosch <nbosch@google.com>
Commit-Queue: Lasse Nielsen <lrn@google.com>
2022-02-22 10:47:41 +00:00

342 lines
13 KiB
Dart

// 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 "package:expect/expect.dart";
import "dart:convert";
import "dart:typed_data";
main() {
testMediaType();
testRoundTrip("");
testRoundTrip("a");
testRoundTrip("ab");
testRoundTrip("abc");
testRoundTrip("abcd");
testRoundTrip("Content with special%25 characters: # ? = % # ? = %");
testRoundTrip("blåbærgrød", utf8);
testRoundTrip("blåbærgrød", latin1);
testUriEquals("data:,abc?d");
testUriEquals("DATA:,ABC?D");
testUriEquals("data:,a%20bc?d");
testUriEquals("DATA:,A%20BC?D");
testUriEquals("data:,abc?d%23e"); // # must and will be is escaped.
// Test that UriData.uri normalizes path and query.
testUtf8Encoding("\u1000\uffff");
testBytes();
testInvalidCharacters();
testNormalization();
testErrors();
}
void testMediaType() {
for (var mimeType in ["", "text/plain", "Text/PLAIN", "text/javascript"]) {
for (var charset in ["", "US-ASCII", "UTF-8"]) {
for (var base64 in ["", ";base64"]) {
bool isBase64 = base64.isNotEmpty;
// Parsing the URI from source:
var charsetParameter = charset.isEmpty ? "" : ";charset=$charset";
var text = "data:$mimeType$charsetParameter$base64,";
var uri = UriData.parse(text);
String expectedCharset = charset.isEmpty ? "US-ASCII" : charset;
String expectedMimeType = mimeType.isEmpty ? "text/plain" : mimeType;
Expect.equals(text, "$uri");
Expect.equals(expectedMimeType, uri.mimeType);
Expect.isTrue(uri.isMimeType(expectedMimeType));
Expect.isTrue(uri.isMimeType(expectedMimeType.toUpperCase()));
Expect.isTrue(uri.isMimeType(expectedMimeType.toLowerCase()));
Expect.equals(expectedCharset, uri.charset);
Expect.isTrue(uri.isCharset(expectedCharset));
Expect.isTrue(uri.isCharset(expectedCharset.toLowerCase()));
Expect.isTrue(uri.isCharset(expectedCharset.toUpperCase()));
var expectedEncoding = Encoding.getByName(expectedCharset);
if (expectedEncoding != null) {
Expect.isTrue(uri.isEncoding(expectedEncoding));
}
Expect.equals(isBase64, uri.isBase64);
// Creating the URI using a constructor:
var encoding = Encoding.getByName(charset);
uri = UriData.fromString("",
mimeType: mimeType, encoding: encoding, base64: isBase64);
expectedMimeType =
(mimeType.isEmpty || mimeType.toLowerCase() == "text/plain")
? "text/plain"
: mimeType;
expectedEncoding = encoding;
expectedCharset = expectedEncoding?.name ?? "US-ASCII";
var expectedText = "data:"
"${expectedMimeType == "text/plain" ? "" : expectedMimeType}"
"${charset.isEmpty ? "" : ";charset=$expectedCharset"}"
"${isBase64 ? ";base64" : ""}"
",";
Expect.equals(expectedText, "$uri");
Expect.equals(expectedMimeType, uri.mimeType);
Expect.isTrue(uri.isMimeType(expectedMimeType));
Expect.isTrue(uri.isMimeType(expectedMimeType.toUpperCase()));
Expect.isTrue(uri.isMimeType(expectedMimeType.toLowerCase()));
Expect.equals(expectedCharset, uri.charset);
Expect.isTrue(uri.isCharset(expectedCharset));
Expect.isTrue(uri.isCharset(expectedCharset.toLowerCase()));
Expect.isTrue(uri.isCharset(expectedCharset.toUpperCase()));
if (expectedEncoding != null) {
Expect.isTrue(uri.isEncoding(expectedEncoding));
}
Expect.equals(isBase64, uri.isBase64);
}
}
}
}
void testRoundTrip(String content, [Encoding? encoding]) {
UriData dataUri = new UriData.fromString(content, encoding: encoding);
Expect.isFalse(dataUri.isBase64);
Uri uri = dataUri.uri;
expectUriEquals(new Uri.dataFromString(content, encoding: encoding), uri);
if (encoding != null) {
UriData dataUriParams =
new UriData.fromString(content, parameters: {"charset": encoding.name});
Expect.equals("$dataUri", "$dataUriParams");
}
Expect.equals(encoding ?? ascii, Encoding.getByName(dataUri.charset));
Expect.equals(content, dataUri.contentAsString(encoding: encoding));
Expect.equals(content, dataUri.contentAsString());
Expect.equals(content, (encoding ?? ascii).decode(dataUri.contentAsBytes()));
uri = dataUri.uri;
Expect.equals(uri.toString(), dataUri.toString());
Expect.equals(dataUri.toString(), new UriData.fromUri(uri).toString());
dataUri = new UriData.fromBytes(content.codeUnits);
Expect.listEquals(content.codeUnits, dataUri.contentAsBytes());
Expect.equals(content, dataUri.contentAsString(encoding: latin1));
uri = dataUri.uri;
Expect.equals(uri.toString(), dataUri.toString());
Expect.equals(dataUri.toString(), new UriData.fromUri(uri).toString());
// Check that the URI is properly normalized.
expectUriEquals(uri, Uri.parse("$uri"));
}
void testUtf8Encoding(String content) {
UriData uri = new UriData.fromString(content, encoding: utf8);
Expect.equals(content, uri.contentAsString(encoding: utf8));
Expect.listEquals(utf8.encode(content), uri.contentAsBytes());
}
void testInvalidCharacters() {
// SPACE, CTL and tspecial, plus '%' and '#' (URI gen-delim)
// This contains all ASCII character that are not valid in attribute/value
// parts.
var invalid =
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x7f'
' ()<>@,;:"/[]?=%#\x80\u{1000}\u{10000}';
var invalidNoSlash = invalid.replaceAll('/', '');
var dataUri = new UriData.fromString(invalid,
encoding: utf8,
mimeType: "$invalidNoSlash/$invalidNoSlash",
parameters: {invalid: invalid});
Expect.equals(invalid, dataUri.contentAsString());
Expect.equals("$invalidNoSlash/$invalidNoSlash", dataUri.mimeType);
Expect.equals(invalid, dataUri.parameters[invalid]);
var uri = dataUri.uri;
Expect.equals("$uri", "$dataUri");
expectUriEquals(uri, Uri.parse("$uri")); // Check that it's canonicalized.
Expect.equals("$dataUri", new UriData.fromUri(uri).toString());
}
void testBytes() {
void testList(List<int> list) {
var dataUri = new UriData.fromBytes(list);
Expect.equals("application/octet-stream", dataUri.mimeType);
Expect.isTrue(dataUri.isBase64);
Expect.listEquals(list, dataUri.contentAsBytes());
dataUri = new UriData.fromBytes(list, percentEncoded: true);
Expect.equals("application/octet-stream", dataUri.mimeType);
Expect.isFalse(dataUri.isBase64);
Expect.listEquals(list, dataUri.contentAsBytes());
var string = new String.fromCharCodes(list);
dataUri = new UriData.fromString(string, encoding: latin1);
Expect.equals("text/plain", dataUri.mimeType);
Expect.isFalse(dataUri.isBase64);
Expect.listEquals(list, dataUri.contentAsBytes());
dataUri = new UriData.fromString(string, encoding: latin1, base64: true);
Expect.equals("text/plain", dataUri.mimeType);
Expect.isTrue(dataUri.isBase64);
Expect.listEquals(list, dataUri.contentAsBytes());
}
void testLists(List<int> list) {
testList(list);
for (int i = 0; i < 27; i++) {
testList(list.sublist(i, i + i)); // All lengths from 0 to 27.
}
}
var bytes = new Uint8List(512);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = i;
}
testLists(bytes);
testLists(new List.from(bytes));
testLists(new List.unmodifiable(bytes));
}
void testNormalization() {
// Base-64 normalization.
// Normalized URI-alphabet characters.
Expect.equals(
"data:;base64,AA/+", UriData.parse("data:;base64,AA_-").toString());
// Normalized escapes.
Expect.equals(
"data:;base64,AB==", UriData.parse("data:;base64,A%42=%3D").toString());
Expect.equals("data:;base64,/+/+",
UriData.parse("data:;base64,%5F%2D%2F%2B").toString());
// Normalized padded data.
Expect.equals(
"data:;base64,AA==", UriData.parse("data:;base64,AA%3D%3D").toString());
Expect.equals(
"data:;base64,AAA=", UriData.parse("data:;base64,AAA%3D").toString());
// Normalized unpadded data.
Expect.equals(
"data:;base64,AA==", UriData.parse("data:;base64,AA").toString());
Expect.equals(
"data:;base64,AAA=", UriData.parse("data:;base64,AAA").toString());
// "URI normalization" of non-base64 content.
var uri = UriData.parse("data:,\x20\xa0");
Expect.equals("data:,%20%C2%A0", uri.toString());
uri = UriData.parse("data:,x://x@y:[z]:42/p/./?q=x&y=z#?#\u1234\u{12345}");
Expect.equals(
"data:,x://x@y:%5Bz%5D:42/p/./?q=x&y=z%23?%23%E1%88%B4%F0%92%8D%85",
uri.toString());
}
void testErrors() {
// Invalid constructor parameters.
Expect.throwsArgumentError(
() => new UriData.fromBytes([], mimeType: "noslash"));
Expect.throwsArgumentError(() => new UriData.fromBytes([257]));
Expect.throwsArgumentError(() => new UriData.fromBytes([-1]));
Expect.throwsArgumentError(() => new UriData.fromBytes([0x10000000]));
Expect.throwsArgumentError(
() => new UriData.fromString("", mimeType: "noslash"));
Expect.throwsArgumentError(
() => new Uri.dataFromBytes([], mimeType: "noslash"));
Expect.throwsArgumentError(() => new Uri.dataFromBytes([257]));
Expect.throwsArgumentError(() => new Uri.dataFromBytes([-1]));
Expect.throwsArgumentError(() => new Uri.dataFromBytes([0x10000000]));
Expect.throwsArgumentError(
() => new Uri.dataFromString("", mimeType: "noslash"));
// Empty parameters allowed, not an error.
var uri = new UriData.fromString("", mimeType: "", parameters: {});
Expect.equals("data:,", "$uri");
// Empty parameter key or value is an error.
Expect.throwsArgumentError(
() => new UriData.fromString("", parameters: {"": "X"}));
Expect.throwsArgumentError(
() => new UriData.fromString("", parameters: {"X": ""}));
// Not recognizing charset is an error.
uri = UriData.parse("data:;charset=arglebargle,X");
Expect.throws(() {
uri.contentAsString();
});
// Doesn't throw if we specify the encoding.
Expect.equals("X", uri.contentAsString(encoding: ascii));
// Parse format.
Expect.throwsFormatException(() => UriData.parse("notdata:,"));
Expect.throwsFormatException(() => UriData.parse("text/plain,noscheme"));
Expect.throwsFormatException(() => UriData.parse("data:noseparator"));
Expect.throwsFormatException(() => UriData.parse("data:noslash,text"));
Expect.throwsFormatException(
() => UriData.parse("data:type/sub;noequals,text"));
Expect.throwsFormatException(() => UriData.parse("data:type/sub;knocomma="));
Expect.throwsFormatException(
() => UriData.parse("data:type/sub;k=v;nocomma"));
Expect.throwsFormatException(() => UriData.parse("data:type/sub;k=nocomma"));
Expect.throwsFormatException(() => UriData.parse("data:type/sub;k=v;base64"));
void formatError(String input) {
Expect.throwsFormatException(
() => UriData.parse("data:;base64,$input"), input);
}
// Invalid base64 format (detected when parsed).
for (var a = 0; a <= 4; a++) {
for (var p = 0; p <= 4; p++) {
// Base-64 encoding must have length divisible by four and no more
// than two padding characters at the end.
if (p < 3 && (a + p) % 4 == 0) continue;
if (p == 0 && a > 1) continue;
formatError("A" * a + "=" * p);
formatError("A" * a + "%3D" * p);
}
}
// Invalid base64 encoding: padding not at end.
formatError("AA=A");
formatError("A=AA");
formatError("=AAA");
formatError("A==A");
formatError("==AA");
formatError("===A");
formatError("AAA%3D=");
formatError("A%3D==");
// Invalid unpadded data.
formatError("A");
formatError("AAAAA");
// Invalid characters.
formatError("AAA*");
formatError("AAA\x00");
formatError("AAA\\");
formatError("AAA,");
// Invalid escapes.
formatError("AAA%25");
formatError("AAA%7F");
formatError("AAA%7F");
}
/// Checks that two [Uri]s are exactly the same.
expectUriEquals(Uri expect, Uri actual) {
Expect.equals(expect.scheme, actual.scheme, "scheme");
Expect.equals(expect.hasAuthority, actual.hasAuthority, "hasAuthority");
Expect.equals(expect.userInfo, actual.userInfo, "userInfo");
Expect.equals(expect.host, actual.host, "host");
Expect.equals(expect.hasPort, actual.hasPort, "hasPort");
Expect.equals(expect.port, actual.port, "port");
Expect.equals(expect.port, actual.port, "port");
Expect.equals(expect.hasQuery, actual.hasQuery, "hasQuery");
Expect.equals(expect.query, actual.query, "query");
Expect.equals(expect.hasFragment, actual.hasFragment, "hasFragment");
Expect.equals(expect.fragment, actual.fragment, "fragment");
}
void testUriEquals(String uriText) {
var data = UriData.parse(uriText);
var uri = Uri.parse(uriText);
Expect.equals(data.uri, uri);
Expect.equals(data.toString(), uri.data.toString());
Expect.equals(data.toString(), uri.toString());
}