diff options
author | Marcos Casagrande <marcoscvp90@gmail.com> | 2020-06-08 18:08:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-08 18:08:26 +0200 |
commit | d9071339443a1da3fbb4c65fa19b4f65328da2f3 (patch) | |
tree | f31b69e1701ba445870e0505e4d034005b4ea8f0 /cli/js | |
parent | 4feccdd3b7d7b1c4ef63c4ccc572a52403135df0 (diff) |
fix(cli/web/fetch): multipart/form-data request body support for binary files (#5886)
Diffstat (limited to 'cli/js')
-rw-r--r-- | cli/js/web/fetch.ts | 45 | ||||
-rw-r--r-- | cli/js/web/fetch/multipart.ts | 81 |
2 files changed, 88 insertions, 38 deletions
diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts index 47ed1a7d1..045d1afcd 100644 --- a/cli/js/web/fetch.ts +++ b/cli/js/web/fetch.ts @@ -2,15 +2,15 @@ import { notImplemented } from "../util.ts"; import { isTypedArray } from "./util.ts"; import * as domTypes from "./dom_types.d.ts"; -import { TextDecoder, TextEncoder } from "./text_encoding.ts"; +import { TextEncoder } from "./text_encoding.ts"; import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob.ts"; import { read } from "../ops/io.ts"; import { close } from "../ops/resources.ts"; import { fetch as opFetch, FetchResponse } from "../ops/fetch.ts"; import * as Body from "./body.ts"; -import { DomFileImpl } from "./dom_file.ts"; import { getHeaderValueParams } from "./util.ts"; import { ReadableStreamImpl } from "./streams/readable_stream.ts"; +import { MultipartBuilder } from "./fetch/multipart.ts"; const NULL_BODY_STATUS = [101, 204, 205, 304]; const REDIRECT_STATUS = [301, 302, 303, 307, 308]; @@ -232,45 +232,14 @@ export async function fetch( body = init.body[blobBytesSymbol]; contentType = init.body.type; } else if (init.body instanceof FormData) { - let boundary = ""; + let boundary; if (headers.has("content-type")) { const params = getHeaderValueParams("content-type"); - if (params.has("boundary")) { - boundary = params.get("boundary")!; - } - } - if (!boundary) { - boundary = - "----------" + - Array.from(Array(32)) - .map(() => Math.random().toString(36)[2] || 0) - .join(""); - } - - let payload = ""; - for (const [fieldName, fieldValue] of init.body.entries()) { - let part = `\r\n--${boundary}\r\n`; - part += `Content-Disposition: form-data; name=\"${fieldName}\"`; - if (fieldValue instanceof DomFileImpl) { - part += `; filename=\"${fieldValue.name}\"`; - } - part += "\r\n"; - if (fieldValue instanceof DomFileImpl) { - part += `Content-Type: ${ - fieldValue.type || "application/octet-stream" - }\r\n`; - } - part += "\r\n"; - if (fieldValue instanceof DomFileImpl) { - part += new TextDecoder().decode(fieldValue[blobBytesSymbol]); - } else { - part += fieldValue; - } - payload += part; + boundary = params.get("boundary")!; } - payload += `\r\n--${boundary}--`; - body = new TextEncoder().encode(payload); - contentType = "multipart/form-data; boundary=" + boundary; + const multipartBuilder = new MultipartBuilder(init.body, boundary); + body = multipartBuilder.getBody(); + contentType = multipartBuilder.getContentType(); } else { // TODO: ReadableStream notImplemented(); diff --git a/cli/js/web/fetch/multipart.ts b/cli/js/web/fetch/multipart.ts index 792f9b5ee..654d4a0ea 100644 --- a/cli/js/web/fetch/multipart.ts +++ b/cli/js/web/fetch/multipart.ts @@ -1,5 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "../../buffer.ts"; +import { bytesSymbol } from "../blob.ts"; +import { DomFileImpl } from "../dom_file.ts"; import { DenoBlob } from "../blob.ts"; import { TextEncoder, TextDecoder } from "../text_encoding.ts"; import { getHeaderValueParams } from "../util.ts"; @@ -14,6 +17,84 @@ interface MultipartHeaders { disposition: Map<string, string>; } +export class MultipartBuilder { + readonly boundary: string; + readonly formData: FormData; + readonly writer: Buffer; + constructor(formData: FormData, boundary?: string) { + this.boundary = boundary ?? this.#createBoundary(); + this.formData = formData; + this.writer = new Buffer(); + } + + getContentType(): string { + return `multipart/form-data; boundary=${this.boundary}`; + } + + getBody(): Uint8Array { + for (const [fieldName, fieldValue] of this.formData.entries()) { + if (fieldValue instanceof DomFileImpl) { + this.#writeFile(fieldName, fieldValue); + } else this.#writeField(fieldName, fieldValue as string); + } + + this.writer.writeSync(encoder.encode(`\r\n--${this.boundary}--`)); + + return this.writer.bytes(); + } + + #createBoundary = (): string => { + return ( + "----------" + + Array.from(Array(32)) + .map(() => Math.random().toString(36)[2] || 0) + .join("") + ); + }; + + #writeHeaders = (headers: string[][]): void => { + let buf = this.writer.empty() ? "" : "\r\n"; + + buf += `--${this.boundary}\r\n`; + for (const [key, value] of headers) { + buf += `${key}: ${value}\r\n`; + } + buf += `\r\n`; + + this.writer.write(encoder.encode(buf)); + }; + + #writeFileHeaders = ( + field: string, + filename: string, + type?: string + ): void => { + const headers = [ + [ + "Content-Disposition", + `form-data; name="${field}"; filename="${filename}"`, + ], + ["Content-Type", type || "application/octet-stream"], + ]; + return this.#writeHeaders(headers); + }; + + #writeFieldHeaders = (field: string): void => { + const headers = [["Content-Disposition", `form-data; name="${field}"`]]; + return this.#writeHeaders(headers); + }; + + #writeField = (field: string, value: string): void => { + this.#writeFieldHeaders(field); + this.writer.writeSync(encoder.encode(value)); + }; + + #writeFile = (field: string, value: DomFileImpl): void => { + this.#writeFileHeaders(field, value.name, value.type); + this.writer.writeSync(value[bytesSymbol]); + }; +} + export class MultipartParser { readonly boundary: string; readonly boundaryChars: Uint8Array; |