diff --git a/fetch/data-urls/README.md b/fetch/data-urls/README.md new file mode 100644 index 00000000000000..64c3d7e952156e --- /dev/null +++ b/fetch/data-urls/README.md @@ -0,0 +1,11 @@ +== data: URLs == + +`resources/data-urls.json` contains `data:` URL tests. The tests are encoded as a JSON array. Each value in the array is an array of two or three values. The first value describes the input, the second value describes the expected MIME type, null if the input is expected to fail somehow, or the empty string if the expected value is `text/plain;charset=US-ASCII`. The third value, if present, describes the expected body as an array of integers representing bytes. + +These tests are used for `data:` URLs in this directory (see `processing.any.js`). + +== Forgiving-base64 decode == + +`resources/base64.json` contains [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode) tests. The tests are encoded as a JSON array. Each value in the array is an array of two values. The first value describes the input, the second value describes the output as an array of integers representing bytes or null if the input cannot be decoded. + +These tests are used for `data:` URLs in this directory (see `base64.any.js`) and `window.atob()` in `../../html/webappapis/atob/base64.html`. diff --git a/fetch/data-urls/base64.any.js b/fetch/data-urls/base64.any.js new file mode 100644 index 00000000000000..48e4c530a86225 --- /dev/null +++ b/fetch/data-urls/base64.any.js @@ -0,0 +1,16 @@ +promise_test(() => fetch("resources/base64.json").then(res => res.json()).then(runBase64Tests), "Setup."); +function runBase64Tests(tests) { + for(let i = 0; i < tests.length; i++) { + const input = tests[i][0], + output = tests[i][1], + dataURL = "data:;base64," + input; + promise_test(t => { + if(output === null) { + return promise_rejects(t, new TypeError(), fetch(dataURL)); + } + return fetch(dataURL).then(res => res.arrayBuffer()).then(body => { + assert_array_equals(new Uint8Array(body), output); + }); + }, "data: URL base64 handling: " + format_value(input)); + } +} diff --git a/fetch/data-urls/processing.any.js b/fetch/data-urls/processing.any.js new file mode 100644 index 00000000000000..b66a874f45ca30 --- /dev/null +++ b/fetch/data-urls/processing.any.js @@ -0,0 +1,20 @@ +promise_test(() => fetch("resources/data-urls.json").then(res => res.json()).then(runDataURLTests), "Setup."); +function runDataURLTests(tests) { + for(let i = 0; i < tests.length; i++) { + const input = tests[i][0], + expectedMimeType = tests[i][1], + expectedBody = expectedMimeType !== null ? tests[i][2] : null; + promise_test(t => { + if(expectedMimeType === null) { + return promise_rejects(t, new TypeError(), fetch(input)); + } else { + return fetch(input).then(res => { + return res.arrayBuffer().then(body => { + assert_array_equals(new Uint8Array(body), expectedBody); + assert_equals(res.headers.get("content-type"), expectedMimeType); // We could assert this earlier, but this fails often + }); + }); + } + }, format_value(input)); + } +} diff --git a/fetch/data-urls/resources/base64.json b/fetch/data-urls/resources/base64.json new file mode 100644 index 00000000000000..7de20b28286195 --- /dev/null +++ b/fetch/data-urls/resources/base64.json @@ -0,0 +1,79 @@ +[ + ["", []], + ["abcd", [105, 183, 29]], + [" abcd", [105, 183, 29]], + ["abcd ", [105, 183, 29]], + [" abcd===", null], + ["abcd=== ", null], + ["abcd ===", null], + ["a", null], + ["ab", [105]], + ["abc", [105, 183]], + ["abcde", null], + ["𐀀", null], + ["=", null], + ["==", null], + ["===", null], + ["====", null], + ["=====", null], + ["a=", null], + ["a==", null], + ["a===", null], + ["a====", null], + ["a=====", null], + ["ab=", null], + ["ab==", [105]], + ["ab===", null], + ["ab====", null], + ["ab=====", null], + ["abc=", [105, 183]], + ["abc==", null], + ["abc===", null], + ["abc====", null], + ["abc=====", null], + ["abcd=", null], + ["abcd==", null], + ["abcd===", null], + ["abcd====", null], + ["abcd=====", null], + ["abcde=", null], + ["abcde==", null], + ["abcde===", null], + ["abcde====", null], + ["abcde=====", null], + ["=a", null], + ["=a=", null], + ["a=b", null], + ["a=b=", null], + ["ab=c", null], + ["ab=c=", null], + ["abc=d", null], + ["abc=d=", null], + ["ab\tcd", [105, 183, 29]], + ["ab\ncd", [105, 183, 29]], + ["ab\fcd", [105, 183, 29]], + ["ab\rcd", [105, 183, 29]], + ["ab cd", [105, 183, 29]], + ["ab\u00a0cd", null], + ["ab\t\n\f\r cd", [105, 183, 29]], + [" \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", [105, 183, 29]], + ["ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", [105]], + ["A", null], + ["/A", [252]], + ["//A", [255, 240]], + ["///A", [255, 255, 192]], + ["////A", null], + ["/", null], + ["A/", [3]], + ["AA/", [0, 15]], + ["AAAA/", null], + ["AAA/", [0, 0, 63]], + ["\u0000nonsense", null], + ["abcd\u0000nonsense", null], + ["YQ", [97]], + ["YR", [97]], + ["~~", null], + ["..", null], + ["--", null], + ["__", null] +] diff --git a/fetch/data-urls/resources/data-urls.json b/fetch/data-urls/resources/data-urls.json new file mode 100644 index 00000000000000..2265b970495883 --- /dev/null +++ b/fetch/data-urls/resources/data-urls.json @@ -0,0 +1,199 @@ +[ + ["data://test/,X", + "text/plain;charset=US-ASCII", + [88]], + ["data://test:test/,X", + null], + ["data:,X", + "text/plain;charset=US-ASCII", + [88]], + ["data:", + null], + ["data:text/html", + null], + ["data:text/html ;charset=x ", + null], + ["data:,", + "text/plain;charset=US-ASCII", + []], + ["data:,X#X", + "text/plain;charset=US-ASCII", + [88]], + ["data:,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:text/plain,X", + "text/plain", + [88]], + ["data:text/plain ,X", + "text/plain", + [88]], + ["data:text/plain%20,X", + "text/plain%20", + [88]], + ["data:text/plain\f,X", + "text/plain%0c", + [88]], + ["data:text/plain%0C,X", + "text/plain%0c", + [88]], + ["data:text/plain;,X", + "text/plain", + [88]], + ["data:;x=x;charset=x,X", + "text/plain;x=x;charset=x", + [88]], + ["data:;x=x,X", + "text/plain;x=x", + [88]], + ["data:text/plain;charset=windows-1252,%C2%B1", + "text/plain;charset=windows-1252", + [194, 177]], + ["data:text/plain;Charset=UTF-8,%C2%B1", + "text/plain;charset=UTF-8", + [194, 177]], + ["data:image/gif,%C2%B1", + "image/gif", + [194, 177]], + ["data:IMAGE/gif,%C2%B1", + "image/gif", + [194, 177]], + ["data:IMAGE/gif;hi=x,%C2%B1", + "image/gif;hi=x", + [194, 177]], + ["data:IMAGE/gif;CHARSET=x,%C2%B1", + "image/gif;charset=x", + [194, 177]], + ["data: ,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:%20,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:\f,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:%1F,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:\u0000,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:%00,%FF", + "text/plain;charset=US-ASCII", + [255]], + ["data:text/html ,X", + "text/html", + [88]], + ["data:text / html,X", + "text/plain;charset=US-ASCII", + [88]], + ["data:†,X", + "text/plain;charset=US-ASCII", + [88]], + ["data:†/†,X", + "%e2%80%a0/%e2%80%a0", + [88]], + ["data:X,X", + "text/plain;charset=US-ASCII", + [88]], + ["data:image/png,X X", + "image/png", + [88, 32, 88]], + ["data:application/xml,X X", + "application/xml", + [88, 32, 88]], + ["data:unknown/unknown,X X", + "unknown/unknown", + [88, 32, 88]], + ["data:text/plain;a=\",\",X", + "text/plain", + [34, 44, 88]], + ["data:text/plain;a=%2C,X", + "text/plain;a=%2C", + [88]], + ["data:;base64;base64,WA", + "text/plain", + [88]], + ["data:x/x;base64;base64,WA", + "x/x", + [88]], + ["data:x/x;base64;charset=x,WA", + "x/x;charset=x", + [87, 65]], + ["data:x/x;base64;charset=x;base64,WA", + "x/x;charset=x", + [88]], + ["data:x/x;base64;base64x,WA", + "x/x", + [87, 65]], + ["data:;base64,W%20A", + "text/plain;charset=US-ASCII", + [88]], + ["data:;base64,W%0CA", + "text/plain;charset=US-ASCII", + [88]], + ["data:x;base64x,WA", + "text/plain;charset=US-ASCII", + [87, 65]], + ["data:x;base64;x,WA", + "text/plain;charset=US-ASCII", + [87, 65]], + ["data:x;base64=x,WA", + "text/plain;charset=US-ASCII", + [87, 65]], + ["data:; base64,WA", + "text/plain;charset=US-ASCII", + [88]], + ["data:; base64,WA", + "text/plain;charset=US-ASCII", + [88]], + ["data: ;charset=x ; base64,WA", + "text/plain;charset=x", + [88]], + ["data:;base64;,WA", + "text/plain", + [87, 65]], + ["data:;base64 ,WA", + "text/plain;charset=US-ASCII", + [88]], + ["data:;base64 ,WA", + "text/plain;charset=US-ASCII", + [88]], + ["data:;base 64,WA", + "text/plain", + [87, 65]], + ["data:;BASe64,WA", + "text/plain;charset=US-ASCII", + [88]], + ["data:;%62ase64,WA", + "text/plain", + [87, 65]], + ["data:%3Bbase64,WA", + "text/plain;charset=US-ASCII", + [87, 65]], + ["data:;charset=x,X", + "text/plain;charset=x", + [88]], + ["data:; charset=x,X", + "text/plain;charset=x", + [88]], + ["data:;charset =x,X", + "text/plain", + [88]], + ["data:;charset= x,X", + "text/plain;charset=\" x\"", + [88]], + ["data:;charset=,X", + "text/plain", + [88]], + ["data:;charset,X", + "text/plain", + [88]], + ["data:;charset=\"x\",X", + "text/plain;charset=x", + [88]], + ["data:;CHARSET=\"X\",X", + "text/plain;charset=X", + [88]] +] diff --git a/html/webappapis/atob/base64.html b/html/webappapis/atob/base64.html index c33ab228f94a95..c522afdd56c7ca 100644 --- a/html/webappapis/atob/base64.html +++ b/html/webappapis/atob/base64.html @@ -70,124 +70,6 @@ // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. } -/** - * Implementation of atob() according to the HTML spec, except that instead of - * throwing INVALID_CHARACTER_ERR we return null. - */ -function myatob(input) { - // WebIDL requires DOMStrings to just be converted using ECMAScript - // ToString, which in our case amounts to calling String(). - input = String(input); - - // "Remove all space characters from input." - input = input.replace(/[ \t\n\f\r]/g, ""); - - // "If the length of input divides by 4 leaving no remainder, then: if - // input ends with one or two U+003D EQUALS SIGN (=) characters, remove - // them from input." - if (input.length % 4 == 0 && /==?$/.test(input)) { - input = input.replace(/==?$/, ""); - } - - // "If the length of input divides by 4 leaving a remainder of 1, throw an - // INVALID_CHARACTER_ERR exception and abort these steps." - // - // "If input contains a character that is not in the following list of - // characters and character ranges, throw an INVALID_CHARACTER_ERR - // exception and abort these steps: - // - // U+002B PLUS SIGN (+) - // U+002F SOLIDUS (/) - // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9) - // U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z - // U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z" - if (input.length % 4 == 1 - || !/^[+/0-9A-Za-z]*$/.test(input)) { - return null; - } - - // "Let output be a string, initially empty." - var output = ""; - - // "Let buffer be a buffer that can have bits appended to it, initially - // empty." - // - // We append bits via left-shift and or. accumulatedBits is used to track - // when we've gotten to 24 bits. - var buffer = 0; - var accumulatedBits = 0; - - // "While position does not point past the end of input, run these - // substeps:" - for (var i = 0; i < input.length; i++) { - // "Find the character pointed to by position in the first column of - // the following table. Let n be the number given in the second cell of - // the same row." - // - // "Append to buffer the six bits corresponding to number, most - // significant bit first." - // - // atobLookup() implements the table from the spec. - buffer <<= 6; - buffer |= atobLookup(input[i]); - - // "If buffer has accumulated 24 bits, interpret them as three 8-bit - // big-endian numbers. Append the three characters with code points - // equal to those numbers to output, in the same order, and then empty - // buffer." - accumulatedBits += 6; - if (accumulatedBits == 24) { - output += String.fromCharCode((buffer & 0xff0000) >> 16); - output += String.fromCharCode((buffer & 0xff00) >> 8); - output += String.fromCharCode(buffer & 0xff); - buffer = accumulatedBits = 0; - } - - // "Advance position by one character." - } - - // "If buffer is not empty, it contains either 12 or 18 bits. If it - // contains 12 bits, discard the last four and interpret the remaining - // eight as an 8-bit big-endian number. If it contains 18 bits, discard the - // last two and interpret the remaining 16 as two 8-bit big-endian numbers. - // Append the one or two characters with code points equal to those one or - // two numbers to output, in the same order." - if (accumulatedBits == 12) { - buffer >>= 4; - output += String.fromCharCode(buffer); - } else if (accumulatedBits == 18) { - buffer >>= 2; - output += String.fromCharCode((buffer & 0xff00) >> 8); - output += String.fromCharCode(buffer & 0xff); - } - - // "Return output." - return output; -} - -/** - * A lookup table for atob(), which converts an ASCII character to the - * corresponding six-bit number. - */ -function atobLookup(chr) { - if (/[A-Z]/.test(chr)) { - return chr.charCodeAt(0) - "A".charCodeAt(0); - } - if (/[a-z]/.test(chr)) { - return chr.charCodeAt(0) - "a".charCodeAt(0) + 26; - } - if (/[0-9]/.test(chr)) { - return chr.charCodeAt(0) - "0".charCodeAt(0) + 52; - } - if (chr == "+") { - return 62; - } - if (chr == "/") { - return 63; - } - // Throw exception; should not be hit in tests -} - function btoaException(input) { input = String(input); for (var i = 0; i < input.length; i++) { @@ -252,55 +134,40 @@ generate_tests(testBtoa, tests); -function testAtob(input) { - var expected = myatob(input); - if (expected === null) { - assert_throws("InvalidCharacterError", function() { atob(input) }); - return; - } - - assert_equals(atob(input), expected); -} - -var tests = ["", "abcd", " abcd", "abcd ", " abcd===", "abcd=== ", - "abcd ===", "a", "ab", "abc", "abcde", String.fromCharCode(0xd800, 0xdc00), - "=", "==", "===", "====", "=====", - "a=", "a==", "a===", "a====", "a=====", - "ab=", "ab==", "ab===", "ab====", "ab=====", - "abc=", "abc==", "abc===", "abc====", "abc=====", - "abcd=", "abcd==", "abcd===", "abcd====", "abcd=====", - "abcde=", "abcde==", "abcde===", "abcde====", "abcde=====", - "=a", "=a=", "a=b", "a=b=", "ab=c", "ab=c=", "abc=d", "abc=d=", - // With whitespace - "ab\tcd", "ab\ncd", "ab\fcd", "ab\rcd", "ab cd", "ab\u00a0cd", - "ab\t\n\f\r cd", " \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", - "ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", - // Test if any bits are set at the end. These should all be fine, since - // they end with A, which becomes 0: - "A", "/A", "//A", "///A", "////A", - // These are all bad, since they end in / (= 63, all bits set) but their - // length isn't a multiple of four characters, so they can't be output by - // btoa(). Thus one might expect some UAs to throw exceptions or otherwise - // object, since they could never be output by btoa(), so they're good to - // test. - "/", "A/", "AA/", "AAAA/", - // But this one is possible: - "AAA/", - // Binary-safety tests - "\0nonsense", "abcd\0nonsense", - // WebIDL tests - undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0, - {toString: function() { return "foo" }}, - {toString: function() { return "abcd" }}, +promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup."); + +const idlTests = [ + [undefined, null], + [null, [158, 233, 101]], + [7, null], + [12, [215]], + [1.5, null], + [true, [182, 187]], + [false, null], + [NaN, [53, 163]], + [+Infinity, [34, 119, 226, 158, 43, 114]], + [-Infinity, null], + [0, null], + [-0, null], + [{toString: function() { return "foo" }}, [126, 138]], + [{toString: function() { return "abcd" }}, [105, 183, 29]] ]; -tests = tests.map( - function(elem) { - if (myatob(elem) === null) { - return ["atob(" + format_value(elem) + ") must raise InvalidCharacterError", elem]; - } - return ["atob(" + format_value(elem) + ") == " + format_value(myatob(elem)), elem]; - } -); -generate_tests(testAtob, tests); +function runAtobTests(tests) { + const allTests = tests.concat(idlTests); + for(let i = 0; i < allTests.length; i++) { + const input = allTests[i][0], + output = allTests[i][1]; + test(() => { + if(output === null) { + assert_throws("InvalidCharacterError", () => window.atob(input)); + } else { + const result = window.atob(input); + for(let ii = 0; ii < output.length; ii++) { + assert_equals(result.charCodeAt(ii), output[ii]); + } + } + }, "atob(" + format_value(input) + ")"); + } +}