summaryrefslogtreecommitdiff
path: root/extensions/fetch/21_formdata.js
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2021-08-11 12:27:05 +0200
committerGitHub <noreply@github.com>2021-08-11 12:27:05 +0200
commita0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch)
tree90671b004537e20f9493fd3277ffd21d30b39a0e /extensions/fetch/21_formdata.js
parent3a6994115176781b3a93d70794b1b81bc95e42b4 (diff)
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'extensions/fetch/21_formdata.js')
-rw-r--r--extensions/fetch/21_formdata.js507
1 files changed, 0 insertions, 507 deletions
diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js
deleted file mode 100644
index 25ed32c2d..000000000
--- a/extensions/fetch/21_formdata.js
+++ /dev/null
@@ -1,507 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-// @ts-check
-/// <reference path="../webidl/internal.d.ts" />
-/// <reference path="../web/internal.d.ts" />
-/// <reference path="../web/lib.deno_web.d.ts" />
-/// <reference path="./internal.d.ts" />
-/// <reference path="../web/06_streams_types.d.ts" />
-/// <reference path="./lib.deno_fetch.d.ts" />
-/// <reference lib="esnext" />
-"use strict";
-
-((window) => {
- const core = window.Deno.core;
- const webidl = globalThis.__bootstrap.webidl;
- const { Blob, File } = globalThis.__bootstrap.file;
- const {
- ArrayPrototypeMap,
- ArrayPrototypePush,
- ArrayPrototypeSlice,
- ArrayPrototypeSplice,
- ArrayPrototypeFilter,
- ArrayPrototypeForEach,
- Map,
- MapPrototypeGet,
- MapPrototypeSet,
- MathRandom,
- Symbol,
- SymbolToStringTag,
- StringFromCharCode,
- StringPrototypeTrim,
- StringPrototypeSlice,
- StringPrototypeSplit,
- StringPrototypeReplace,
- StringPrototypeIndexOf,
- StringPrototypePadStart,
- StringPrototypeCodePointAt,
- StringPrototypeReplaceAll,
- TypeError,
- TypedArrayPrototypeSubarray,
- } = window.__bootstrap.primordials;
-
- const entryList = Symbol("entry list");
-
- /**
- * @param {string} name
- * @param {string | Blob} value
- * @param {string | undefined} filename
- * @returns {FormDataEntry}
- */
- function createEntry(name, value, filename) {
- if (value instanceof Blob && !(value instanceof File)) {
- value = new File([value], "blob", { type: value.type });
- }
- if (value instanceof File && filename !== undefined) {
- value = new File([value], filename, {
- type: value.type,
- lastModified: value.lastModified,
- });
- }
- return {
- name,
- // @ts-expect-error because TS is not smart enough
- value,
- };
- }
-
- /**
- * @typedef FormDataEntry
- * @property {string} name
- * @property {FormDataEntryValue} value
- */
-
- class FormData {
- get [SymbolToStringTag]() {
- return "FormData";
- }
-
- /** @type {FormDataEntry[]} */
- [entryList] = [];
-
- /** @param {void} form */
- constructor(form) {
- if (form !== undefined) {
- webidl.illegalConstructor();
- }
- this[webidl.brand] = webidl.brand;
- }
-
- /**
- * @param {string} name
- * @param {string | Blob} valueOrBlobValue
- * @param {string} [filename]
- * @returns {void}
- */
- append(name, valueOrBlobValue, filename) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'append' on 'FormData'";
- webidl.requiredArguments(arguments.length, 2, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
- if (valueOrBlobValue instanceof Blob) {
- valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
- prefix,
- context: "Argument 2",
- });
- if (filename !== undefined) {
- filename = webidl.converters["USVString"](filename, {
- prefix,
- context: "Argument 3",
- });
- }
- } else {
- valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
- prefix,
- context: "Argument 2",
- });
- }
-
- const entry = createEntry(name, valueOrBlobValue, filename);
-
- ArrayPrototypePush(this[entryList], entry);
- }
-
- /**
- * @param {string} name
- * @returns {void}
- */
- delete(name) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'name' on 'FormData'";
- webidl.requiredArguments(arguments.length, 1, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
-
- const list = this[entryList];
- for (let i = 0; i < list.length; i++) {
- if (list[i].name === name) {
- ArrayPrototypeSplice(list, i, 1);
- i--;
- }
- }
- }
-
- /**
- * @param {string} name
- * @returns {FormDataEntryValue | null}
- */
- get(name) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'get' on 'FormData'";
- webidl.requiredArguments(arguments.length, 1, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
-
- for (const entry of this[entryList]) {
- if (entry.name === name) return entry.value;
- }
- return null;
- }
-
- /**
- * @param {string} name
- * @returns {FormDataEntryValue[]}
- */
- getAll(name) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'getAll' on 'FormData'";
- webidl.requiredArguments(arguments.length, 1, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
-
- const returnList = [];
- for (const entry of this[entryList]) {
- if (entry.name === name) ArrayPrototypePush(returnList, entry.value);
- }
- return returnList;
- }
-
- /**
- * @param {string} name
- * @returns {boolean}
- */
- has(name) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'has' on 'FormData'";
- webidl.requiredArguments(arguments.length, 1, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
-
- for (const entry of this[entryList]) {
- if (entry.name === name) return true;
- }
- return false;
- }
-
- /**
- * @param {string} name
- * @param {string | Blob} valueOrBlobValue
- * @param {string} [filename]
- * @returns {void}
- */
- set(name, valueOrBlobValue, filename) {
- webidl.assertBranded(this, FormData);
- const prefix = "Failed to execute 'set' on 'FormData'";
- webidl.requiredArguments(arguments.length, 2, { prefix });
-
- name = webidl.converters["USVString"](name, {
- prefix,
- context: "Argument 1",
- });
- if (valueOrBlobValue instanceof Blob) {
- valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
- prefix,
- context: "Argument 2",
- });
- if (filename !== undefined) {
- filename = webidl.converters["USVString"](filename, {
- prefix,
- context: "Argument 3",
- });
- }
- } else {
- valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
- prefix,
- context: "Argument 2",
- });
- }
-
- const entry = createEntry(name, valueOrBlobValue, filename);
-
- const list = this[entryList];
- let added = false;
- for (let i = 0; i < list.length; i++) {
- if (list[i].name === name) {
- if (!added) {
- list[i] = entry;
- added = true;
- } else {
- ArrayPrototypeSplice(list, i, 1);
- i--;
- }
- }
- }
- if (!added) {
- ArrayPrototypePush(list, entry);
- }
- }
- }
-
- webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
-
- webidl.configurePrototype(FormData);
-
- const escape = (str, isFilename) =>
- StringPrototypeReplace(
- StringPrototypeReplace(
- StringPrototypeReplace(
- (isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n")),
- /\n/g,
- "%0A",
- ),
- /\r/g,
- "%0D",
- ),
- /"/g,
- "%22",
- );
-
- /**
- * convert FormData to a Blob synchronous without reading all of the files
- * @param {globalThis.FormData} formData
- */
- function formDataToBlob(formData) {
- const boundary = StringPrototypePadStart(
- StringPrototypeSlice(
- StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""),
- -28,
- ),
- 32,
- "-",
- );
- const chunks = [];
- const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`;
-
- for (const [name, value] of formData) {
- if (typeof value === "string") {
- ArrayPrototypePush(
- chunks,
- prefix + escape(name) + '"' + CRLF + CRLF +
- StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF,
- );
- } else {
- ArrayPrototypePush(
- chunks,
- prefix + escape(name) + `"; filename="${escape(value.name, true)}"` +
- CRLF +
- `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`,
- value,
- CRLF,
- );
- }
- }
-
- ArrayPrototypePush(chunks, `--${boundary}--`);
-
- return new Blob(chunks, {
- type: "multipart/form-data; boundary=" + boundary,
- });
- }
-
- /**
- * @param {string} value
- * @returns {Map<string, string>}
- */
- function parseContentDisposition(value) {
- /** @type {Map<string, string>} */
- const params = new Map();
- // Forced to do so for some Map constructor param mismatch
- ArrayPrototypeForEach(
- ArrayPrototypeMap(
- ArrayPrototypeFilter(
- ArrayPrototypeMap(
- ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1),
- (s) => StringPrototypeSplit(StringPrototypeTrim(s), "="),
- ),
- (arr) => arr.length > 1,
- ),
- ([k, v]) => [k, StringPrototypeReplace(v, /^"([^"]*)"$/, "$1")],
- ),
- ([k, v]) => MapPrototypeSet(params, k, v),
- );
-
- return params;
- }
-
- const CRLF = "\r\n";
- const LF = StringPrototypeCodePointAt(CRLF, 1);
- const CR = StringPrototypeCodePointAt(CRLF, 0);
-
- class MultipartParser {
- /**
- * @param {Uint8Array} body
- * @param {string | undefined} boundary
- */
- constructor(body, boundary) {
- if (!boundary) {
- throw new TypeError("multipart/form-data must provide a boundary");
- }
-
- this.boundary = `--${boundary}`;
- this.body = body;
- this.boundaryChars = core.encode(this.boundary);
- }
-
- /**
- * @param {string} headersText
- * @returns {{ headers: Headers, disposition: Map<string, string> }}
- */
- #parseHeaders(headersText) {
- const headers = new Headers();
- const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
- for (const rawHeader of rawHeaders) {
- const sepIndex = StringPrototypeIndexOf(rawHeader, ":");
- if (sepIndex < 0) {
- continue; // Skip this header
- }
- const key = StringPrototypeSlice(rawHeader, 0, sepIndex);
- const value = StringPrototypeSlice(rawHeader, sepIndex + 1);
- headers.set(key, value);
- }
-
- const disposition = parseContentDisposition(
- headers.get("Content-Disposition") ?? "",
- );
-
- return { headers, disposition };
- }
-
- /**
- * @returns {FormData}
- */
- parse() {
- // Body must be at least 2 boundaries + \r\n + -- on the last boundary.
- if (this.body.length < (this.boundary.length * 2) + 4) {
- throw new TypeError("Form data too short to be valid.");
- }
-
- const formData = new FormData();
- let headerText = "";
- let boundaryIndex = 0;
- let state = 0;
- let fileStart = 0;
-
- for (let i = 0; i < this.body.length; i++) {
- const byte = this.body[i];
- const prevByte = this.body[i - 1];
- const isNewLine = byte === LF && prevByte === CR;
-
- if (state === 1 || state === 2 || state == 3) {
- headerText += StringFromCharCode(byte);
- }
- if (state === 0 && isNewLine) {
- state = 1;
- } else if (state === 1 && isNewLine) {
- state = 2;
- const headersDone = this.body[i + 1] === CR &&
- this.body[i + 2] === LF;
-
- if (headersDone) {
- state = 3;
- }
- } else if (state === 2 && isNewLine) {
- state = 3;
- } else if (state === 3 && isNewLine) {
- state = 4;
- fileStart = i + 1;
- } else if (state === 4) {
- if (this.boundaryChars[boundaryIndex] !== byte) {
- boundaryIndex = 0;
- } else {
- boundaryIndex++;
- }
-
- if (boundaryIndex >= this.boundary.length) {
- const { headers, disposition } = this.#parseHeaders(headerText);
- const content = TypedArrayPrototypeSubarray(
- this.body,
- fileStart,
- i - boundaryIndex - 1,
- );
- // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
- const filename = MapPrototypeGet(disposition, "filename");
- const name = MapPrototypeGet(disposition, "name");
-
- state = 5;
- // Reset
- boundaryIndex = 0;
- headerText = "";
-
- if (!name) {
- continue; // Skip, unknown name
- }
-
- if (filename) {
- const blob = new Blob([content], {
- type: headers.get("Content-Type") || "application/octet-stream",
- });
- formData.append(name, blob, filename);
- } else {
- formData.append(name, core.decode(content));
- }
- }
- } else if (state === 5 && isNewLine) {
- state = 1;
- }
- }
-
- return formData;
- }
- }
-
- /**
- * @param {Uint8Array} body
- * @param {string | undefined} boundary
- * @returns {FormData}
- */
- function parseFormData(body, boundary) {
- const parser = new MultipartParser(body, boundary);
- return parser.parse();
- }
-
- /**
- * @param {FormDataEntry[]} entries
- * @returns {FormData}
- */
- function formDataFromEntries(entries) {
- const fd = new FormData();
- fd[entryList] = entries;
- return fd;
- }
-
- webidl.converters["FormData"] = webidl
- .createInterfaceConverter("FormData", FormData);
-
- globalThis.__bootstrap.formData = {
- FormData,
- formDataToBlob,
- parseFormData,
- formDataFromEntries,
- };
-})(globalThis);