Skip to content

Commit d24c7a0

Browse files
committed
data: URL tests
Tests for whatwg/fetch#579.
1 parent 5d149f0 commit d24c7a0

File tree

4 files changed

+128
-167
lines changed

4 files changed

+128
-167
lines changed

fetch/data-urls/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
== Forgiving-base64 decode ==
2+
3+
`resources/base64.json` contains [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode) tests. The tests are encoded as a list. Each item in the list is a list of two items. The first item describes the input, the second item describes the output as a list of integers representing bytes or null if the input cannot be decoded.
4+
5+
These tests are used for `data:` URLs in this directory (see `base64.any.js`) and `window.atob()` in `../../html/webappapis/atob/base64.html`.

fetch/data-urls/base64.any.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
promise_test(() => fetch("resources/base64.json").then(res => res.json()).then(runBase64Tests), "Setup.");
2+
function runBase64Tests(tests) {
3+
for(let i = 0; i < tests.length; i++) {
4+
const input = tests[i][0],
5+
output = tests[i][1],
6+
dataURL = "data:;base64," + input;
7+
promise_test(t => {
8+
if(output === null) {
9+
return promise_rejects(t, new TypeError(), fetch(dataURL));
10+
}
11+
return fetch(dataURL).then(res => res.arrayBuffer()).then(body => {
12+
assert_array_equals(new Uint8Array(body), output);
13+
});
14+
}, "data: URL base64 handling: " + format_value(input));
15+
}
16+
}

fetch/data-urls/resources/base64.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
[
2+
["", []],
3+
["abcd", [105, 183, 29]],
4+
[" abcd", [105, 183, 29]],
5+
["abcd ", [105, 183, 29]],
6+
[" abcd===", null],
7+
["abcd=== ", null],
8+
["abcd ===", null],
9+
["a", null],
10+
["ab", [105]],
11+
["abc", [105, 183]],
12+
["abcde", null],
13+
["𐀀", null],
14+
["=", null],
15+
["==", null],
16+
["===", null],
17+
["====", null],
18+
["=====", null],
19+
["a=", null],
20+
["a==", null],
21+
["a===", null],
22+
["a====", null],
23+
["a=====", null],
24+
["ab=", null],
25+
["ab==", [105]],
26+
["ab===", null],
27+
["ab====", null],
28+
["ab=====", null],
29+
["abc=", [105, 183]],
30+
["abc==", null],
31+
["abc===", null],
32+
["abc====", null],
33+
["abc=====", null],
34+
["abcd=", null],
35+
["abcd==", null],
36+
["abcd===", null],
37+
["abcd====", null],
38+
["abcd=====", null],
39+
["abcde=", null],
40+
["abcde==", null],
41+
["abcde===", null],
42+
["abcde====", null],
43+
["abcde=====", null],
44+
["=a", null],
45+
["=a=", null],
46+
["a=b", null],
47+
["a=b=", null],
48+
["ab=c", null],
49+
["ab=c=", null],
50+
["abc=d", null],
51+
["abc=d=", null],
52+
["ab\tcd", [105, 183, 29]],
53+
["ab\ncd", [105, 183, 29]],
54+
["ab\fcd", [105, 183, 29]],
55+
["ab\rcd", [105, 183, 29]],
56+
["ab cd", [105, 183, 29]],
57+
["ab\u00a0cd", null],
58+
["ab\t\n\f\r cd", [105, 183, 29]],
59+
[" \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", [105, 183, 29]],
60+
["ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", [105]],
61+
["A", null],
62+
["/A", [252]],
63+
["//A", [255, 240]],
64+
["///A", [255, 255, 192]],
65+
["////A", null],
66+
["/", null],
67+
["A/", [3]],
68+
["AA/", [0, 15]],
69+
["AAAA/", null],
70+
["AAA/", [0, 0, 63]],
71+
["\u0000nonsense", null],
72+
["abcd\u0000nonsense", null]
73+
]

html/webappapis/atob/base64.html

Lines changed: 34 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -70,124 +70,6 @@
7070
// Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
7171
}
7272

73-
/**
74-
* Implementation of atob() according to the HTML spec, except that instead of
75-
* throwing INVALID_CHARACTER_ERR we return null.
76-
*/
77-
function myatob(input) {
78-
// WebIDL requires DOMStrings to just be converted using ECMAScript
79-
// ToString, which in our case amounts to calling String().
80-
input = String(input);
81-
82-
// "Remove all space characters from input."
83-
input = input.replace(/[ \t\n\f\r]/g, "");
84-
85-
// "If the length of input divides by 4 leaving no remainder, then: if
86-
// input ends with one or two U+003D EQUALS SIGN (=) characters, remove
87-
// them from input."
88-
if (input.length % 4 == 0 && /==?$/.test(input)) {
89-
input = input.replace(/==?$/, "");
90-
}
91-
92-
// "If the length of input divides by 4 leaving a remainder of 1, throw an
93-
// INVALID_CHARACTER_ERR exception and abort these steps."
94-
//
95-
// "If input contains a character that is not in the following list of
96-
// characters and character ranges, throw an INVALID_CHARACTER_ERR
97-
// exception and abort these steps:
98-
//
99-
// U+002B PLUS SIGN (+)
100-
// U+002F SOLIDUS (/)
101-
// U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)
102-
// U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z
103-
// U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z"
104-
if (input.length % 4 == 1
105-
|| !/^[+/0-9A-Za-z]*$/.test(input)) {
106-
return null;
107-
}
108-
109-
// "Let output be a string, initially empty."
110-
var output = "";
111-
112-
// "Let buffer be a buffer that can have bits appended to it, initially
113-
// empty."
114-
//
115-
// We append bits via left-shift and or. accumulatedBits is used to track
116-
// when we've gotten to 24 bits.
117-
var buffer = 0;
118-
var accumulatedBits = 0;
119-
120-
// "While position does not point past the end of input, run these
121-
// substeps:"
122-
for (var i = 0; i < input.length; i++) {
123-
// "Find the character pointed to by position in the first column of
124-
// the following table. Let n be the number given in the second cell of
125-
// the same row."
126-
//
127-
// "Append to buffer the six bits corresponding to number, most
128-
// significant bit first."
129-
//
130-
// atobLookup() implements the table from the spec.
131-
buffer <<= 6;
132-
buffer |= atobLookup(input[i]);
133-
134-
// "If buffer has accumulated 24 bits, interpret them as three 8-bit
135-
// big-endian numbers. Append the three characters with code points
136-
// equal to those numbers to output, in the same order, and then empty
137-
// buffer."
138-
accumulatedBits += 6;
139-
if (accumulatedBits == 24) {
140-
output += String.fromCharCode((buffer & 0xff0000) >> 16);
141-
output += String.fromCharCode((buffer & 0xff00) >> 8);
142-
output += String.fromCharCode(buffer & 0xff);
143-
buffer = accumulatedBits = 0;
144-
}
145-
146-
// "Advance position by one character."
147-
}
148-
149-
// "If buffer is not empty, it contains either 12 or 18 bits. If it
150-
// contains 12 bits, discard the last four and interpret the remaining
151-
// eight as an 8-bit big-endian number. If it contains 18 bits, discard the
152-
// last two and interpret the remaining 16 as two 8-bit big-endian numbers.
153-
// Append the one or two characters with code points equal to those one or
154-
// two numbers to output, in the same order."
155-
if (accumulatedBits == 12) {
156-
buffer >>= 4;
157-
output += String.fromCharCode(buffer);
158-
} else if (accumulatedBits == 18) {
159-
buffer >>= 2;
160-
output += String.fromCharCode((buffer & 0xff00) >> 8);
161-
output += String.fromCharCode(buffer & 0xff);
162-
}
163-
164-
// "Return output."
165-
return output;
166-
}
167-
168-
/**
169-
* A lookup table for atob(), which converts an ASCII character to the
170-
* corresponding six-bit number.
171-
*/
172-
function atobLookup(chr) {
173-
if (/[A-Z]/.test(chr)) {
174-
return chr.charCodeAt(0) - "A".charCodeAt(0);
175-
}
176-
if (/[a-z]/.test(chr)) {
177-
return chr.charCodeAt(0) - "a".charCodeAt(0) + 26;
178-
}
179-
if (/[0-9]/.test(chr)) {
180-
return chr.charCodeAt(0) - "0".charCodeAt(0) + 52;
181-
}
182-
if (chr == "+") {
183-
return 62;
184-
}
185-
if (chr == "/") {
186-
return 63;
187-
}
188-
// Throw exception; should not be hit in tests
189-
}
190-
19173
function btoaException(input) {
19274
input = String(input);
19375
for (var i = 0; i < input.length; i++) {
@@ -252,55 +134,40 @@
252134

253135
generate_tests(testBtoa, tests);
254136

255-
function testAtob(input) {
256-
var expected = myatob(input);
257-
if (expected === null) {
258-
assert_throws("InvalidCharacterError", function() { atob(input) });
259-
return;
260-
}
261-
262-
assert_equals(atob(input), expected);
263-
}
264-
265-
var tests = ["", "abcd", " abcd", "abcd ", " abcd===", "abcd=== ",
266-
"abcd ===", "a", "ab", "abc", "abcde", String.fromCharCode(0xd800, 0xdc00),
267-
"=", "==", "===", "====", "=====",
268-
"a=", "a==", "a===", "a====", "a=====",
269-
"ab=", "ab==", "ab===", "ab====", "ab=====",
270-
"abc=", "abc==", "abc===", "abc====", "abc=====",
271-
"abcd=", "abcd==", "abcd===", "abcd====", "abcd=====",
272-
"abcde=", "abcde==", "abcde===", "abcde====", "abcde=====",
273-
"=a", "=a=", "a=b", "a=b=", "ab=c", "ab=c=", "abc=d", "abc=d=",
274-
// With whitespace
275-
"ab\tcd", "ab\ncd", "ab\fcd", "ab\rcd", "ab cd", "ab\u00a0cd",
276-
"ab\t\n\f\r cd", " \t\n\f\r ab\t\n\f\r cd\t\n\f\r ",
277-
"ab\t\n\f\r =\t\n\f\r =\t\n\f\r ",
278-
// Test if any bits are set at the end. These should all be fine, since
279-
// they end with A, which becomes 0:
280-
"A", "/A", "//A", "///A", "////A",
281-
// These are all bad, since they end in / (= 63, all bits set) but their
282-
// length isn't a multiple of four characters, so they can't be output by
283-
// btoa(). Thus one might expect some UAs to throw exceptions or otherwise
284-
// object, since they could never be output by btoa(), so they're good to
285-
// test.
286-
"/", "A/", "AA/", "AAAA/",
287-
// But this one is possible:
288-
"AAA/",
289-
// Binary-safety tests
290-
"\0nonsense", "abcd\0nonsense",
291-
// WebIDL tests
292-
undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,
293-
{toString: function() { return "foo" }},
294-
{toString: function() { return "abcd" }},
137+
promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup.");
138+
139+
const idlTests = [
140+
[undefined, null],
141+
[null, [158, 233, 101]],
142+
[7, null],
143+
[12, [215]],
144+
[1.5, null],
145+
[true, [182, 187]],
146+
[false, null],
147+
[NaN, [53, 163]],
148+
[+Infinity, [34, 119, 226, 158, 43, 114]],
149+
[-Infinity, null],
150+
[0, null],
151+
[-0, null],
152+
[{toString: function() { return "foo" }}, [126, 138]],
153+
[{toString: function() { return "abcd" }}, [105, 183, 29]]
295154
];
296-
tests = tests.map(
297-
function(elem) {
298-
if (myatob(elem) === null) {
299-
return ["atob(" + format_value(elem) + ") must raise InvalidCharacterError", elem];
300-
}
301-
return ["atob(" + format_value(elem) + ") == " + format_value(myatob(elem)), elem];
302-
}
303-
);
304155

305-
generate_tests(testAtob, tests);
156+
function runAtobTests(tests) {
157+
const allTests = tests.concat(idlTests);
158+
for(let i = 0; i < allTests.length; i++) {
159+
const input = allTests[i][0],
160+
output = allTests[i][1];
161+
test(() => {
162+
if(output === null) {
163+
assert_throws("InvalidCharacterError", () => window.atob(input));
164+
} else {
165+
const result = window.atob(input);
166+
for(let ii = 0; ii < output.length; ii++) {
167+
assert_equals(result.charCodeAt(ii), output[ii]);
168+
}
169+
}
170+
}, "atob(" + format_value(input) + ")");
171+
}
172+
}
306173
</script>

0 commit comments

Comments
 (0)