diff --git a/FileAPI/file/send-file-formdata-controls.tentative.html b/FileAPI/file/send-file-formdata-controls.html similarity index 92% rename from FileAPI/file/send-file-formdata-controls.tentative.html rename to FileAPI/file/send-file-formdata-controls.html index 4259741b63ef31..2982b13be70722 100644 --- a/FileAPI/file/send-file-formdata-controls.tentative.html +++ b/FileAPI/file/send-file-formdata-controls.html @@ -1,10 +1,6 @@ -FormData: Upload files named using controls (tentative) - +FormData: Upload files named using controls -FormData: Upload files named using punctuation (tentative) - +FormData: Upload files named using punctuation +
@@ -30,32 +31,85 @@ customElements.define('my-control', MyControl); const $ = document.querySelector.bind(document); -function submitPromise(t) { +function submitPromise(t, extractFromIframe) { + if (!extractFromIframe) { + extractFromIframe = (iframe) => iframe.contentWindow.location.search; + } return new Promise((resolve, reject) => { const iframe = $('iframe'); - iframe.onload = () => resolve(iframe.contentWindow.location.search); + iframe.onload = () => resolve(extractFromIframe(iframe)); iframe.onerror = () => reject(new Error('iframe onerror fired')); $('form').submit(); }); } -function testSerializedEntry({name, expectedName, value, expectedValue, description}) { - promise_test(async t => { - $('#container').innerHTML = '
' + - '' + - '
' + - ''; - if (name !== undefined) { - $('my-control').setAttribute("name", name); - } - if (Array.isArray(value)) { - $('my-control').setValues(value); - } else { - $('my-control').value = value; - } - const query = await submitPromise(t); - assert_equals(query, `?${expectedName}=${expectedValue}`); - }, description); +function testSerializedEntry({name, value, expected, description}) { + // urlencoded + { + const {name: expectedName, value: expectedValue} = expected.urlencoded; + promise_test(async t => { + $('#container').innerHTML = '
' + + '' + + '
' + + ''; + if (name !== undefined) { + $('my-control').setAttribute("name", name); + } + if (Array.isArray(value)) { + $('my-control').setValues(value); + } else { + $('my-control').value = value; + } + const query = await submitPromise(t); + assert_equals(query, `?${expectedName}=${expectedValue}`); + }, `${description} (urlencoded)`); + } + + // formdata + { + const {name: expectedName, filename: expectedFilename, value: expectedValue} = expected.formdata; + promise_test(async t => { + $('#container').innerHTML = + '
' + + '' + + '
' + + ''; + if (name !== undefined) { + $('my-control').setAttribute("name", name); + } + if (Array.isArray(value)) { + $('my-control').setValues(value); + } else { + $('my-control').value = value; + } + const escaped = await submitPromise(t, iframe => iframe.contentDocument.body.textContent); + const formdata = escaped + .replace(/\r\n?|\n/g, "\r\n") + .replace( + /\\x[0-9A-Fa-f]{2}/g, + escape => String.fromCodePoint(parseInt(escape.substring(2), 16)) + ); + const boundary = formdata.split("\r\n")[0]; + const expected = [ + boundary, + ...(() => { + if (expectedFilename === undefined) { + return [`Content-Disposition: form-data; name="${expectedName}"`]; + } else { + return [ + `Content-Disposition: form-data; name="${expectedName}"; filename="${expectedFilename}"`, + "Content-Type: text/plain" + ]; + } + })(), + "", + expectedValue, + boundary + "--", + "" + ].join("\r\n"); + assert_equals(formdata, expected); + }, `${description} (formdata)`); + } } promise_test(t => { @@ -145,152 +199,380 @@ testSerializedEntry({ name: 'a\nb', value: 'c', - expectedName: 'a%0D%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\n in name' }); testSerializedEntry({ name: 'a\rb', value: 'c', - expectedName: 'a%0D%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\r in name' }); testSerializedEntry({ name: 'a\r\nb', value: 'c', - expectedName: 'a%0D%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\r\\n in name' }); testSerializedEntry({ name: 'a\n\rb', value: 'c', - expectedName: 'a%0D%0A%0D%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0A%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0A%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\n\\r in name' }); testSerializedEntry({ name: 'a', value: 'b\nc', - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\n in value' }); testSerializedEntry({ name: 'a', value: 'b\rc', - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\r in value' }); testSerializedEntry({ name: 'a', value: 'b\r\nc', - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\r\\n in value' }); testSerializedEntry({ name: 'a', value: 'b\n\rc', - expectedName: 'a', - expectedValue: 'b%0D%0A%0D%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0A%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\n\r\nc' + } + }, description: 'Newline normalization - \\n\\r in value' }); testSerializedEntry({ name: 'a', - value: new File([], "b\nc"), - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + value: new File([], "b\nc", {type: "text/plain"}), + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0Ac', + value: '' + } + }, description: 'Newline normalization - \\n in filename' }); testSerializedEntry({ name: 'a', - value: new File([], "b\rc"), - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + value: new File([], "b\rc", {type: "text/plain"}), + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0Dc', + value: '' + } + }, description: 'Newline normalization - \\r in filename' }); testSerializedEntry({ name: 'a', - value: new File([], "b\r\nc"), - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + value: new File([], "b\r\nc", {type: "text/plain"}), + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0D%0Ac', + value: '' + } + }, description: 'Newline normalization - \\r\\n in filename' }); testSerializedEntry({ name: 'a', - value: new File([], "b\n\rc"), - expectedName: 'a', - expectedValue: 'b%0D%0A%0D%0Ac', + value: new File([], "b\n\rc", {type: "text/plain"}), + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0A%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0A%0Dc', + value: '' + } + }, description: 'Newline normalization - \\n\\r in filename' }); testSerializedEntry({ value: [['a\nb', 'c']], - expectedName: 'a%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\n in FormData name' }); testSerializedEntry({ value: [['a\rb', 'c']], - expectedName: 'a%0Db', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\r in FormData name' }); testSerializedEntry({ value: [['a\r\nb', 'c']], - expectedName: 'a%0D%0Ab', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\r\\n in FormData name' }); testSerializedEntry({ value: [['a\n\rb', 'c']], - expectedName: 'a%0A%0Db', - expectedValue: 'c', + expected: { + urlencoded: { + name: 'a%0D%0A%0D%0Ab', + value: 'c' + }, + formdata: { + name: 'a%0D%0A%0D%0Ab', + value: 'c' + } + }, description: 'Newline normalization - \\n\\r in FormData name' }); testSerializedEntry({ value: [['a', 'b\nc']], - expectedName: 'a', - expectedValue: 'b%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\n in FormData value' }); testSerializedEntry({ value: [['a', 'b\rc']], - expectedName: 'a', - expectedValue: 'b%0Dc', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\r in FormData value' }); testSerializedEntry({ value: [['a', 'b\r\nc']], - expectedName: 'a', - expectedValue: 'b%0D%0Ac', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\nc' + } + }, description: 'Newline normalization - \\r\\n in FormData value' }); testSerializedEntry({ value: [['a', 'b\n\rc']], - expectedName: 'a', - expectedValue: 'b%0A%0Dc', + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0A%0D%0Ac' + }, + formdata: { + name: 'a', + value: 'b\r\n\r\nc' + } + }, description: 'Newline normalization - \\n\\r in FormData value' }); + +testSerializedEntry({ + value: [['a', new File([], 'b\nc', {type: "text/plain"})]], + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0Ac', + value: '' + } + }, + description: 'Newline normalization - \\n in FormData filename' +}); + +testSerializedEntry({ + value: [['a', new File([], 'b\rc', {type: "text/plain"})]], + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0Dc', + value: '' + } + }, + description: 'Newline normalization - \\r in FormData filename' +}); + +testSerializedEntry({ + value: [['a', new File([], 'b\r\nc', {type: "text/plain"})]], + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0D%0Ac', + value: '' + } + }, + description: 'Newline normalization - \\r\\n in FormData filename' +}); + +testSerializedEntry({ + value: [['a', new File([], 'b\n\rc', {type: "text/plain"})]], + expected: { + urlencoded: { + name: 'a', + value: 'b%0D%0A%0D%0Ac' + }, + formdata: { + name: 'a', + filename: 'b%0A%0Dc', + value: '' + } + }, + description: 'Newline normalization - \\n\\r in FormData filename' +}); diff --git a/html/semantics/forms/form-submission-0/constructing-form-data-set.html b/html/semantics/forms/form-submission-0/constructing-form-data-set.html index b27f1996d99cb8..a6c5e91554d28f 100644 --- a/html/semantics/forms/form-submission-0/constructing-form-data-set.html +++ b/html/semantics/forms/form-submission-0/constructing-form-data-set.html @@ -114,6 +114,17 @@ form.submit(); }); +test(() => { + const form = populateForm(''); + form.addEventListener('formdata', e => { + e.formData.append('a\nb', 'c\rd'); + }); + const formData = new FormData(form); + const [name, value] = [...formData][0]; + assert_equals(name, 'a\nb'); + assert_equals(value, 'c\rd'); +}, 'Entries added to the "formdata" IDL attribute shouldn\'t be newline normalized in the resulting FormData'); + test(() => { let form = populateForm(''); let formDataInEvent = null; diff --git a/html/semantics/forms/form-submission-0/enctypes-helper.js b/html/semantics/forms/form-submission-0/enctypes-helper.js new file mode 100644 index 00000000000000..53b76b019545a5 --- /dev/null +++ b/html/semantics/forms/form-submission-0/enctypes-helper.js @@ -0,0 +1,138 @@ +(() => { + // Using echo-content-escaped.py rather than + // /fetch/api/resources/echo-content.py to work around WebKit not + // percent-encoding \x00, which causes the response to be detected as + // a binary file and served as a download. + const ACTION_URL = "/FileAPI/file/resources/echo-content-escaped.py"; + + const IFRAME_NAME = "formtargetframe"; + + // Undoes the escapes from /fetch/api/resources/echo-content.py + function unescape(str) { + return str + .replace(/\r\n?|\n/g, "\r\n") + .replace( + /\\x[0-9A-Fa-f]{2}/g, + (escape) => String.fromCodePoint(parseInt(escape.substring(2), 16)), + ) + .replace(/\\\\/g, "\\"); + } + + // `expectedBuilder` is a function that takes in the actual form body + // (necessary to get the multipart/form-data payload) and returns the form + // body that should be expected. + // If `testFormData` is false, the form entry will be submitted in for + // controls. If it is true, it will submitted by modifying the entry list + // during the `formdata` event. + async function formSubmissionTest({ + name, + value, + expectedBuilder, + enctype, + formEncoding, + testFormData = false, + testCase, + }) { + if (document.readyState !== "complete") { + await new Promise((resolve) => addEventListener("load", resolve)); + } + + const formTargetFrame = Object.assign(document.createElement("iframe"), { + name: IFRAME_NAME, + }); + document.body.append(formTargetFrame); + testCase.add_cleanup(() => { + document.body.removeChild(formTargetFrame); + }); + + const form = Object.assign(document.createElement("form"), { + acceptCharset: formEncoding, + action: ACTION_URL, + method: "POST", + enctype, + target: IFRAME_NAME, + }); + document.body.append(form); + testCase.add_cleanup(() => { + document.body.removeChild(form); + }); + + if (!testFormData) { + const input = document.createElement("input"); + input.name = name; + if (value instanceof File) { + input.type = "file"; + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(value); + input.files = dataTransfer.files; + } else { + input.type = "hidden"; + input.value = value; + } + form.append(input); + } else { + form.addEventListener("formdata", (evt) => { + evt.formData.append(name, value); + }); + } + + await new Promise((resolve) => { + form.submit(); + formTargetFrame.onload = resolve; + }); + + const serialized = unescape( + formTargetFrame.contentDocument.body.textContent, + ); + const expected = expectedBuilder(serialized); + assert_equals(serialized, expected); + } + + // This function returns a function to add individual form tests corresponding + // to some enctype. + // `expectedBuilder` is a function that takes two parameters: `expected` (the + // `expected` value of a test) and `serialized` (the actual form body + // submitted by the browser), and returns the correct form body that should + // have been submitted. This is necessary in order to account for th + // multipart/form-data boundary. + window.formSubmissionTemplate = (enctype, expectedBuilder) => { + function form({ + name, + value, + expected, + formEncoding = "utf-8", + description, + }) { + const commonParams = { + name, + value, + expectedBuilder: expectedBuilder.bind(null, expected), + enctype, + formEncoding, + }; + + // Normal form + promise_test( + (testCase) => + formSubmissionTest({ + ...commonParams, + testCase, + }), + `${enctype}: ${description} (normal form)`, + ); + + // formdata event + promise_test( + (testCase) => + formSubmissionTest({ + ...commonParams, + testFormData: true, + testCase, + }), + `${enctype}: ${description} (formdata event)`, + ); + } + + return form; + }; +})(); diff --git a/html/semantics/forms/form-submission-0/multipart-formdata.window.js b/html/semantics/forms/form-submission-0/multipart-formdata.window.js new file mode 100644 index 00000000000000..f26c0723dba4bf --- /dev/null +++ b/html/semantics/forms/form-submission-0/multipart-formdata.window.js @@ -0,0 +1,343 @@ +// META: script=enctypes-helper.js + +// Form submissions in multipart/form-data are also tested in +// /FileAPI/file/send-file* + +const form = formSubmissionTemplate( + "multipart/form-data", + ({ name, filename, value }, serialized) => { + let headers; + if (filename === undefined) { + headers = [`Content-Disposition: form-data; name="${name}"`]; + } else { + headers = [ + `Content-Disposition: form-data; name="${name}"; filename="${filename}"`, + "Content-Type: text/plain", + ]; + } + + const boundary = serialized.split("\r\n")[0]; + + return [ + boundary, + ...headers, + "", + value, + boundary + "--", + "", + ].join("\r\n"); + }, +); + +form({ + name: "basic", + value: "test", + expected: { + name: "basic", + value: "test", + }, + description: "Basic test", +}); + +form({ + name: "basic", + value: new File([], "file-test.txt", { type: "text/plain" }), + expected: { + name: "basic", + filename: "file-test.txt", + value: "", + }, + description: "Basic File test", +}); + +form({ + name: "a\0b", + value: "c", + expected: { + name: "a\0b", + value: "c", + }, + description: "0x00 in name", +}); + +form({ + name: "a", + value: "b\0c", + expected: { + name: "a", + value: "b\0c", + }, + description: "0x00 in value", +}); + +form({ + name: "a", + value: new File([], "b\0c", { type: "text/plain" }), + expected: { + name: "a", + filename: "b\0c", + value: "", + }, + description: "0x00 in filename", +}); + +form({ + name: "a\nb", + value: "c", + expected: { + name: "a%0D%0Ab", + value: "c", + }, + description: "\\n in name", +}); + +form({ + name: "a\rb", + value: "c", + expected: { + name: "a%0D%0Ab", + value: "c", + }, + description: "\\r in name", +}); + +form({ + name: "a\r\nb", + value: "c", + expected: { + name: "a%0D%0Ab", + value: "c", + }, + description: "\\r\\n in name", +}); + +form({ + name: "a\n\rb", + value: "c", + expected: { + name: "a%0D%0A%0D%0Ab", + value: "c", + }, + description: "\\n\\r in name", +}); + +form({ + name: "a", + value: "b\nc", + expected: { + name: "a", + value: "b\r\nc", + }, + description: "\\n in value", +}); + +form({ + name: "a", + value: "b\rc", + expected: { + name: "a", + value: "b\r\nc", + }, + description: "\\r in value", +}); + +form({ + name: "a", + value: "b\r\nc", + expected: { + name: "a", + value: "b\r\nc", + }, + description: "\\r\\n in value", +}); + +form({ + name: "a", + value: "b\n\rc", + expected: { + name: "a", + value: "b\r\n\r\nc", + }, + description: "\\n\\r in value", +}); + +form({ + name: "a", + value: new File([], "b\nc", { type: "text/plain" }), + expected: { + name: "a", + filename: "b%0Ac", + value: "", + }, + description: "\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\rc", { type: "text/plain" }), + expected: { + name: "a", + filename: "b%0Dc", + value: "", + }, + description: "\\r in filename", +}); + +form({ + name: "a", + value: new File([], "b\r\nc", { type: "text/plain" }), + expected: { + name: "a", + filename: "b%0D%0Ac", + value: "", + }, + description: "\\r\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\n\rc", { type: "text/plain" }), + expected: { + name: "a", + filename: "b%0A%0Dc", + value: "", + }, + description: "\\n\\r in filename", +}); + +form({ + name: 'a"b', + value: "c", + expected: { + name: "a%22b", + value: "c", + }, + description: "double quote in name", +}); + +form({ + name: "a", + value: 'b"c', + expected: { + name: "a", + value: 'b"c', + }, + description: "double quote in value", +}); + +form({ + name: "a", + value: new File([], 'b"c', { type: "text/plain" }), + expected: { + name: "a", + filename: "b%22c", + value: "", + }, + description: "double quote in filename", +}); + +form({ + name: "a'b", + value: "c", + expected: { + name: "a'b", + value: "c", + }, + description: "single quote in name", +}); + +form({ + name: "a", + value: "b'c", + expected: { + name: "a", + value: "b'c", + }, + description: "single quote in value", +}); + +form({ + name: "a", + value: new File([], "b'c", { type: "text/plain" }), + expected: { + name: "a", + filename: "b'c", + value: "", + }, + description: "single quote in filename", +}); + +form({ + name: "a\\b", + value: "c", + expected: { + name: "a\\b", + value: "c", + }, + description: "backslash in name", +}); + +form({ + name: "a", + value: "b\\c", + expected: { + name: "a", + value: "b\\c", + }, + description: "backslash in value", +}); + +form({ + name: "a", + value: new File([], "b\\c", { type: "text/plain" }), + expected: { + name: "a", + filename: "b\\c", + value: "", + }, + description: "backslash in filename", +}); + +form({ + name: "áb", + value: "ç", + expected: { + name: "\xC3\xA1b", + value: "\xC3\xA7", + }, + description: "non-ASCII in name and value", +}); + +form({ + name: "a", + value: new File([], "ə.txt", { type: "text/plain" }), + expected: { + name: "a", + filename: "\xC9\x99.txt", + value: "", + }, + description: "non-ASCII in filename", +}); + +form({ + name: "aəb", + value: "c\uFFFDd", + formEncoding: "windows-1252", + expected: { + name: "aəb", + value: "c�d", + }, + description: "characters not in encoding in name and value", +}); + +form({ + name: "á", + value: new File([], "💩", { type: "text/plain" }), + formEncoding: "windows-1252", + expected: { + name: "\xE1", + filename: "💩", + value: "", + }, + description: "character not in encoding in filename", +}); diff --git a/html/semantics/forms/form-submission-0/text-plain.window.js b/html/semantics/forms/form-submission-0/text-plain.window.js new file mode 100644 index 00000000000000..54bca9e169c062 --- /dev/null +++ b/html/semantics/forms/form-submission-0/text-plain.window.js @@ -0,0 +1,218 @@ +// META: script=enctypes-helper.js + +const form = formSubmissionTemplate( + "text/plain", + (expected) => expected, +); + +form({ + name: "basic", + value: "test", + expected: "basic=test\r\n", + description: "Basic test", +}); + +form({ + name: "basic", + value: new File([], "file-test.txt"), + expected: "basic=file-test.txt\r\n", + description: "Basic File test", +}); + +form({ + name: "a\0b", + value: "c", + expected: "a\0b=c\r\n", + description: "0x00 in name", +}); + +form({ + name: "a", + value: "b\0c", + expected: "a=b\0c\r\n", + description: "0x00 in value", +}); + +form({ + name: "a", + value: new File([], "b\0c"), + expected: "a=b\0c\r\n", + description: "0x00 in filename", +}); + +form({ + name: "a\nb", + value: "c", + expected: "a\r\nb=c\r\n", + description: "\\n in name", +}); + +form({ + name: "a\rb", + value: "c", + expected: "a\r\nb=c\r\n", + description: "\\r in name", +}); + +form({ + name: "a\r\nb", + value: "c", + expected: "a\r\nb=c\r\n", + description: "\\r\\n in name", +}); + +form({ + name: "a\n\rb", + value: "c", + expected: "a\r\n\r\nb=c\r\n", + description: "\\n\\r in name", +}); + +form({ + name: "a", + value: "b\nc", + expected: "a=b\r\nc\r\n", + description: "\\n in value", +}); + +form({ + name: "a", + value: "b\rc", + expected: "a=b\r\nc\r\n", + description: "\\r in value", +}); + +form({ + name: "a", + value: "b\r\nc", + expected: "a=b\r\nc\r\n", + description: "\\r\\n in value", +}); + +form({ + name: "a", + value: "b\n\rc", + expected: "a=b\r\n\r\nc\r\n", + description: "\\n\\r in value", +}); + +form({ + name: "a", + value: new File([], "b\nc"), + expected: "a=b\r\nc\r\n", + description: "\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\rc"), + expected: "a=b\r\nc\r\n", + description: "\\r in filename", +}); + +form({ + name: "a", + value: new File([], "b\r\nc"), + expected: "a=b\r\nc\r\n", + description: "\\r\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\n\rc"), + expected: "a=b\r\n\r\nc\r\n", + description: "\\n\\r in filename", +}); + +form({ + name: 'a"b', + value: "c", + expected: 'a"b=c\r\n', + description: "double quote in name", +}); + +form({ + name: "a", + value: 'b"c', + expected: 'a=b"c\r\n', + description: "double quote in value", +}); + +form({ + name: "a", + value: new File([], 'b"c'), + expected: 'a=b"c\r\n', + description: "double quote in filename", +}); + +form({ + name: "a'b", + value: "c", + expected: "a'b=c\r\n", + description: "single quote in name", +}); + +form({ + name: "a", + value: "b'c", + expected: "a=b'c\r\n", + description: "single quote in value", +}); + +form({ + name: "a", + value: new File([], "b'c"), + expected: "a=b'c\r\n", + description: "single quote in filename", +}); + +form({ + name: "a\\b", + value: "c", + expected: "a\\b=c\r\n", + description: "backslash in name", +}); + +form({ + name: "a", + value: "b\\c", + expected: "a=b\\c\r\n", + description: "backslash in value", +}); + +form({ + name: "a", + value: new File([], "b\\c"), + expected: "a=b\\c\r\n", + description: "backslash in filename", +}); + +form({ + name: "áb", + value: "ç", + expected: "\xC3\xA1b=\xC3\xA7\r\n", + description: "non-ASCII in name and value", +}); + +form({ + name: "a", + value: new File([], "ə.txt"), + expected: "a=\xC9\x99.txt\r\n", + description: "non-ASCII in filename", +}); + +form({ + name: "aəb", + value: "c\uFFFDd", + formEncoding: "windows-1252", + expected: "aəb=c�d\r\n", + description: "characters not in encoding in name and value", +}); + +form({ + name: "á", + value: new File([], "💩"), + formEncoding: "windows-1252", + expected: "\xE1=💩\r\n", + description: "character not in encoding in filename", +}); diff --git a/html/semantics/forms/form-submission-0/urlencoded2.window.js b/html/semantics/forms/form-submission-0/urlencoded2.window.js new file mode 100644 index 00000000000000..df86abb093dc56 --- /dev/null +++ b/html/semantics/forms/form-submission-0/urlencoded2.window.js @@ -0,0 +1,218 @@ +// META: script=enctypes-helper.js + +const form = formSubmissionTemplate( + "application/x-www-form-urlencoded", + (expected) => expected, +); + +form({ + name: "basic", + value: "test", + expected: "basic=test", + description: "Basic test", +}); + +form({ + name: "basic", + value: new File([], "file-test.txt"), + expected: "basic=file-test.txt", + description: "Basic File test", +}); + +form({ + name: "a\0b", + value: "c", + expected: "a%00b=c", + description: "0x00 in name", +}); + +form({ + name: "a", + value: "b\0c", + expected: "a=b%00c", + description: "0x00 in value", +}); + +form({ + name: "a", + value: new File([], "b\0c"), + expected: "a=b%00c", + description: "0x00 in filename", +}); + +form({ + name: "a\nb", + value: "c", + expected: "a%0D%0Ab=c", + description: "\\n in name", +}); + +form({ + name: "a\rb", + value: "c", + expected: "a%0D%0Ab=c", + description: "\\r in name", +}); + +form({ + name: "a\r\nb", + value: "c", + expected: "a%0D%0Ab=c", + description: "\\r\\n in name", +}); + +form({ + name: "a\n\rb", + value: "c", + expected: "a%0D%0A%0D%0Ab=c", + description: "\\n\\r in name", +}); + +form({ + name: "a", + value: "b\nc", + expected: "a=b%0D%0Ac", + description: "\\n in value", +}); + +form({ + name: "a", + value: "b\rc", + expected: "a=b%0D%0Ac", + description: "\\r in value", +}); + +form({ + name: "a", + value: "b\r\nc", + expected: "a=b%0D%0Ac", + description: "\\r\\n in value", +}); + +form({ + name: "a", + value: "b\n\rc", + expected: "a=b%0D%0A%0D%0Ac", + description: "\\n\\r in value", +}); + +form({ + name: "a", + value: new File([], "b\nc"), + expected: "a=b%0D%0Ac", + description: "\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\rc"), + expected: "a=b%0D%0Ac", + description: "\\r in filename", +}); + +form({ + name: "a", + value: new File([], "b\r\nc"), + expected: "a=b%0D%0Ac", + description: "\\r\\n in filename", +}); + +form({ + name: "a", + value: new File([], "b\n\rc"), + expected: "a=b%0D%0A%0D%0Ac", + description: "\\n\\r in filename", +}); + +form({ + name: 'a"b', + value: "c", + expected: "a%22b=c", + description: "double quote in name", +}); + +form({ + name: "a", + value: 'b"c', + expected: "a=b%22c", + description: "double quote in value", +}); + +form({ + name: "a", + value: new File([], 'b"c'), + expected: "a=b%22c", + description: "double quote in filename", +}); + +form({ + name: "a'b", + value: "c", + expected: "a%27b=c", + description: "single quote in name", +}); + +form({ + name: "a", + value: "b'c", + expected: "a=b%27c", + description: "single quote in value", +}); + +form({ + name: "a", + value: new File([], "b'c"), + expected: "a=b%27c", + description: "single quote in filename", +}); + +form({ + name: "a\\b", + value: "c", + expected: "a%5Cb=c", + description: "backslash in name", +}); + +form({ + name: "a", + value: "b\\c", + expected: "a=b%5Cc", + description: "backslash in value", +}); + +form({ + name: "a", + value: new File([], "b\\c"), + expected: "a=b%5Cc", + description: "backslash in filename", +}); + +form({ + name: "áb", + value: "ç", + expected: "%C3%A1b=%C3%A7", + description: "non-ASCII in name and value", +}); + +form({ + name: "a", + value: new File([], "ə.txt"), + expected: "a=%C9%99.txt", + description: "non-ASCII in filename", +}); + +form({ + name: "aəb", + value: "c\uFFFDd", + formEncoding: "windows-1252", + expected: "a%26%23601%3Bb=c%26%2365533%3Bd", + description: "characters not in encoding in name and value", +}); + +form({ + name: "á", + value: new File([], "💩"), + formEncoding: "windows-1252", + expected: "%E1=%26%23128169%3B", + description: "character not in encoding in filename", +}); diff --git a/url/urlsearchparams-stringifier.any.js b/url/urlsearchparams-stringifier.any.js index bc7bedd533f58d..6187db64b1747d 100644 --- a/url/urlsearchparams-stringifier.any.js +++ b/url/urlsearchparams-stringifier.any.js @@ -133,3 +133,13 @@ test(() => { assert_equals(url.toString(), 'http://www.example.com/?a=b%2Cc&x=y'); assert_equals(params.toString(), 'a=b%2Cc&x=y'); }, 'URLSearchParams connected to URL'); + +test(() => { + const url = new URL('http://www.example.com/'); + const params = url.searchParams; + + params.append('a\nb', 'c\rd'); + params.append('e\n\rf', 'g\r\nh'); + + assert_equals(params.toString(), "a%0Ab=c%0Dd&e%0A%0Df=g%0D%0Ah"); +}, 'URLSearchParams must not do newline normalization');