summaryrefslogtreecommitdiff
path: root/cli/js
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js')
-rw-r--r--cli/js/web/fetch.ts45
-rw-r--r--cli/js/web/fetch/multipart.ts81
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;