diff options
author | Marcos Casagrande <marcoscvp90@gmail.com> | 2020-06-01 14:32:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-01 14:32:08 +0200 |
commit | 1d3dce9a68c981aded31b4eb12f8a2ec4beecfab (patch) | |
tree | ea21bc746bab92715d5d9621edd272167f61451a /cli/js/web/fetch | |
parent | edeeedf40161dcc4932a33139a7fffa1a73cc142 (diff) |
fix(cli/js/web): formData parser for binary files (#6015)
Diffstat (limited to 'cli/js/web/fetch')
-rw-r--r-- | cli/js/web/fetch/multipart.ts | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/cli/js/web/fetch/multipart.ts b/cli/js/web/fetch/multipart.ts new file mode 100644 index 000000000..792f9b5ee --- /dev/null +++ b/cli/js/web/fetch/multipart.ts @@ -0,0 +1,120 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { DenoBlob } from "../blob.ts"; +import { TextEncoder, TextDecoder } from "../text_encoding.ts"; +import { getHeaderValueParams } from "../util.ts"; + +const decoder = new TextDecoder(); +const encoder = new TextEncoder(); +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); + +interface MultipartHeaders { + headers: Headers; + disposition: Map<string, string>; +} + +export class MultipartParser { + readonly boundary: string; + readonly boundaryChars: Uint8Array; + readonly body: Uint8Array; + constructor(body: Uint8Array, boundary: string) { + if (!boundary) { + throw new TypeError("multipart/form-data must provide a boundary"); + } + + this.boundary = `--${boundary}`; + this.body = body; + this.boundaryChars = encoder.encode(this.boundary); + } + + #parseHeaders = (headersText: string): MultipartHeaders => { + 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); + } + + return { + headers, + disposition: getHeaderValueParams( + headers.get("Content-Disposition") ?? "" + ), + }; + }; + + parse(): FormData { + 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 DenoBlob([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; + } +} |