From ef1a5fd6dece17e810acb30acdfb219966e813df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Sat, 19 Jun 2021 19:01:20 +0200 Subject: [PATCH 1/4] New FormData Serializer --- extensions/fetch/21_formdata.js | 195 ++++++-------------------------- extensions/fetch/22_body.js | 12 +- extensions/fetch/internal.d.ts | 7 +- 3 files changed, 42 insertions(+), 172 deletions(-) diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js index bbf051da121674..236a4b1c4382d8 100644 --- a/extensions/fetch/21_formdata.js +++ b/extensions/fetch/21_formdata.js @@ -242,170 +242,40 @@ webidl.configurePrototype(FormData); - class MultipartBuilder { - /** - * @param {FormData} formData - */ - constructor(formData) { - this.entryList = formData[entryList]; - this.boundary = this.#createBoundary(); - /** @type {Uint8Array[]} */ - this.chunks = []; - } - - /** - * @returns {string} - */ - getContentType() { - return `multipart/form-data; boundary=${this.boundary}`; - } - - /** - * @returns {Uint8Array} - */ - getBody() { - for (const { name, value } of this.entryList) { - if (value instanceof File) { - this.#writeFile(name, value); - } else this.#writeField(name, value); - } - - this.chunks.push(core.encode(`\r\n--${this.boundary}--`)); + const escape = (str) => + str.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22"); - let totalLength = 0; - for (const chunk of this.chunks) { - totalLength += chunk.byteLength; - } - - const finalBuffer = new Uint8Array(totalLength); - let i = 0; - for (const chunk of this.chunks) { - finalBuffer.set(chunk, i); - i += chunk.byteLength; + /** + * convert FormData to a Blob synchronous without reading all of the files + * @param {globalThis.FormData} formData + */ + function formDataToBlob(formData) { + const boundary = `${Math.random()}${Math.random()}` + .replaceAll(".", "").slice(-28).padStart(32, "-"); + const chunks = []; + const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`; + + for (const [name, value] of formData) { + if (typeof value === "string") { + chunks.push( + prefix + escape(name) + '"' + CRLF + CRLF + + value.replace(/\r(?!\n)|(? Math.random().toString(36)[2] || 0) - .join("") - ); } - /** - * @param {[string, string][]} headers - * @returns {void} - */ - #writeHeaders(headers) { - let buf = (this.chunks.length === 0) ? "" : "\r\n"; - - buf += `--${this.boundary}\r\n`; - for (const [key, value] of headers) { - buf += `${key}: ${value}\r\n`; - } - buf += `\r\n`; - - this.chunks.push(core.encode(buf)); - } + chunks.push(`--${boundary}--`); - /** - * @param {string} field - * @param {string} filename - * @param {string} [type] - * @returns {void} - */ - #writeFileHeaders( - field, - filename, - type, - ) { - const escapedField = this.#headerEscape(field); - const escapedFilename = this.#headerEscape(filename, true); - /** @type {[string, string][]} */ - const headers = [ - [ - "Content-Disposition", - `form-data; name="${escapedField}"; filename="${escapedFilename}"`, - ], - ["Content-Type", type || "application/octet-stream"], - ]; - return this.#writeHeaders(headers); - } - - /** - * @param {string} field - * @returns {void} - */ - #writeFieldHeaders(field) { - /** @type {[string, string][]} */ - const headers = [[ - "Content-Disposition", - `form-data; name="${this.#headerEscape(field)}"`, - ]]; - return this.#writeHeaders(headers); - } - - /** - * @param {string} field - * @param {string} value - * @returns {void} - */ - #writeField(field, value) { - this.#writeFieldHeaders(field); - this.chunks.push(core.encode(this.#normalizeNewlines(value))); - } - - /** - * @param {string} field - * @param {File} value - * @returns {void} - */ - #writeFile(field, value) { - this.#writeFileHeaders(field, value.name, value.type); - this.chunks.push(value[_byteSequence]); - } - - /** - * @param {string} string - * @returns {string} - */ - #normalizeNewlines(string) { - return string.replace(/\r(?!\n)|(? Date: Sat, 19 Jun 2021 19:22:47 +0200 Subject: [PATCH 2/4] remove _byteSequence --- extensions/fetch/21_formdata.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js index 236a4b1c4382d8..c790cfd00ca4b2 100644 --- a/extensions/fetch/21_formdata.js +++ b/extensions/fetch/21_formdata.js @@ -13,7 +13,7 @@ ((window) => { const core = window.Deno.core; const webidl = globalThis.__bootstrap.webidl; - const { Blob, File, _byteSequence } = globalThis.__bootstrap.file; + const { Blob, File } = globalThis.__bootstrap.file; const entryList = Symbol("entry list"); @@ -25,10 +25,10 @@ */ function createEntry(name, value, filename) { if (value instanceof Blob && !(value instanceof File)) { - value = new File([value[_byteSequence]], "blob", { type: value.type }); + value = new File([value], "blob", { type: value.type }); } if (value instanceof File && filename !== undefined) { - value = new File([value[_byteSequence]], filename, { + value = new File([value], filename, { type: value.type, lastModified: value.lastModified, }); From 5a151362a9506b577305046c39abe47fb9dbbddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Sun, 20 Jun 2021 02:55:07 +0200 Subject: [PATCH 3/4] correct filename encoding --- extensions/fetch/21_formdata.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js index c790cfd00ca4b2..ed4cd4268ed38f 100644 --- a/extensions/fetch/21_formdata.js +++ b/extensions/fetch/21_formdata.js @@ -242,8 +242,11 @@ webidl.configurePrototype(FormData); - const escape = (str) => - str.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22"); + const escape = (str, isFilename) => + (isFilename ? str : str.replace(/\r?\n|\r/g, "\r\n")) + .replace(/\n/g, "%0A") + .replace(/\r/g, "%0D") + .replace(/"/g, "%22"); /** * convert FormData to a Blob synchronous without reading all of the files @@ -263,7 +266,7 @@ ); } else { chunks.push( - prefix + escape(name) + `"; filename="${escape(value.name)}"` + CRLF + + prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + CRLF + `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, value, CRLF, From 77c97bc08de7c81b81b462fb1e59af39a1d64e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Sun, 20 Jun 2021 03:03:20 +0200 Subject: [PATCH 4/4] auto format --- extensions/fetch/21_formdata.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js index ed4cd4268ed38f..f0033a332548da 100644 --- a/extensions/fetch/21_formdata.js +++ b/extensions/fetch/21_formdata.js @@ -244,9 +244,9 @@ const escape = (str, isFilename) => (isFilename ? str : str.replace(/\r?\n|\r/g, "\r\n")) - .replace(/\n/g, "%0A") - .replace(/\r/g, "%0D") - .replace(/"/g, "%22"); + .replace(/\n/g, "%0A") + .replace(/\r/g, "%0D") + .replace(/"/g, "%22"); /** * convert FormData to a Blob synchronous without reading all of the files @@ -266,7 +266,8 @@ ); } else { chunks.push( - prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + CRLF + + prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + + CRLF + `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, value, CRLF,