summaryrefslogtreecommitdiff
path: root/op_crates/fetch/21_formdata.js
diff options
context:
space:
mode:
authorAndy Hayden <andyhayden1@gmail.com>2021-04-30 12:51:48 -0700
committerGitHub <noreply@github.com>2021-04-30 15:51:48 -0400
commit684c357136fd44f9d5a1b8bb4402400ed1354677 (patch)
treeebc14b1d01b6643dd4d588516692dffc0f8fcb52 /op_crates/fetch/21_formdata.js
parentabaec7a88e991188d885bede652f35d76ab4f340 (diff)
Rename crate_ops to extensions (#10431)
Diffstat (limited to 'op_crates/fetch/21_formdata.js')
-rw-r--r--op_crates/fetch/21_formdata.js552
1 files changed, 0 insertions, 552 deletions
diff --git a/op_crates/fetch/21_formdata.js b/op_crates/fetch/21_formdata.js
deleted file mode 100644
index c50cf4cf7..000000000
--- a/op_crates/fetch/21_formdata.js
+++ /dev/null
@@ -1,552 +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="../file/internal.d.ts" />
-/// <reference path="../file/lib.deno_file.d.ts" />
-/// <reference path="./internal.d.ts" />
-/// <reference path="./11_streams_types.d.ts" />
-/// <reference path="./lib.deno_fetch.d.ts" />
-/// <reference lib="esnext" />
-"use strict";
-
-((window) => {
- const webidl = globalThis.__bootstrap.webidl;
- const { Blob, File, _byteSequence } = globalThis.__bootstrap.file;
-
- 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[_byteSequence]], "blob", { type: value.type });
- }
- if (value instanceof File && filename !== undefined) {
- value = new File([value[_byteSequence]], 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 [Symbol.toStringTag]() {
- 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);
-
- this[entryList].push(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) {
- list.splice(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) returnList.push(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 {
- list.splice(i, 1);
- i--;
- }
- }
- }
- if (!added) {
- list.push(entry);
- }
- }
- }
-
- webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
-
- const encoder = new TextEncoder();
-
- 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(encoder.encode(`\r\n--${this.boundary}--`));
-
- 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;
- }
-
- return finalBuffer;
- }
-
- #createBoundary = () => {
- return (
- "----------" +
- Array.from(Array(32))
- .map(() => 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(encoder.encode(buf));
- };
-
- /**
- * @param {string} field
- * @param {string} filename
- * @param {string} [type]
- * @returns {void}
- */
- #writeFileHeaders = (
- field,
- filename,
- type,
- ) => {
- /** @type {[string, string][]} */
- const headers = [
- [
- "Content-Disposition",
- `form-data; name="${field}"; filename="${filename}"`,
- ],
- ["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="${field}"`]];
- return this.#writeHeaders(headers);
- };
-
- /**
- * @param {string} field
- * @param {string} value
- * @returns {void}
- */
- #writeField = (field, value) => {
- this.#writeFieldHeaders(field);
- this.chunks.push(encoder.encode(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 {FormData} formdata
- * @returns {{body: Uint8Array, contentType: string}}
- */
- function encodeFormData(formdata) {
- const builder = new MultipartBuilder(formdata);
- return {
- body: builder.getBody(),
- contentType: builder.getContentType(),
- };
- }
-
- /**
- * @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
- value
- .split(";")
- .slice(1)
- .map((s) => s.trim().split("="))
- .filter((arr) => arr.length > 1)
- .map(([k, v]) => [k, v.replace(/^"([^"]*)"$/, "$1")])
- .forEach(([k, v]) => params.set(k, v));
- return params;
- }
-
- const LF = "\n".codePointAt(0);
- const CR = "\r".codePointAt(0);
- const decoder = new TextDecoder("utf-8");
-
- 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 = encoder.encode(this.boundary);
- }
-
- /**
- * @param {string} headersText
- * @returns {{ headers: Headers, disposition: Map<string, string> }}
- */
- #parseHeaders = (headersText) => {
- const headers = new Headers();
- const rawHeaders = headersText.split("\r\n");
- for (const rawHeader of rawHeaders) {
- const sepIndex = rawHeader.indexOf(":");
- if (sepIndex < 0) {
- continue; // Skip this header
- }
- const key = rawHeader.slice(0, sepIndex);
- const value = rawHeader.slice(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 += String.fromCharCode(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 = this.body.subarray(
- fileStart,
- i - boundaryIndex - 1,
- );
- // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
- const filename = disposition.get("filename");
- const name = disposition.get("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, decoder.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,
- encodeFormData,
- parseFormData,
- formDataFromEntries,
- };
-})(globalThis);