diff options
author | crowlKats <crowlkats@gmail.com> | 2020-03-17 07:32:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-17 02:32:43 -0400 |
commit | 9833975ef21afced84c6a16724ce13d026f09298 (patch) | |
tree | 1a9d76a9bd12c70d362d6feafc37515c66c446c0 | |
parent | f9557a4ff6b73a4af37e713bb6b2294253c7b230 (diff) |
feat: fetch should accept a FormData body (#4363)
-rw-r--r-- | cli/js/tests/fetch_test.ts | 14 | ||||
-rw-r--r-- | cli/js/web/fetch.ts | 42 | ||||
-rw-r--r-- | cli/js/web/form_data.ts | 12 |
3 files changed, 64 insertions, 4 deletions
diff --git a/cli/js/tests/fetch_test.ts b/cli/js/tests/fetch_test.ts index 146709583..9a705c40a 100644 --- a/cli/js/tests/fetch_test.ts +++ b/cli/js/tests/fetch_test.ts @@ -288,6 +288,20 @@ unitTest({ perms: { net: true } }, async function fetchInitBlobBody(): Promise< assert(response.headers.get("content-type")!.startsWith("text/javascript")); }); +unitTest( + { perms: { net: true } }, + async function fetchInitFormDataBody(): Promise<void> { + const form = new FormData(); + form.append("field", "value"); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: form + }); + const resultForm = await response.formData(); + assertEquals(form.get("field"), resultForm.get("field")); + } +); + unitTest({ perms: { net: true } }, async function fetchUserAgent(): Promise< void > { diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts index acf0bad0f..7ab68d2e4 100644 --- a/cli/js/web/fetch.ts +++ b/cli/js/web/fetch.ts @@ -13,6 +13,7 @@ import { FormData } from "./form_data.ts"; import { URL } from "./url.ts"; import { URLSearchParams } from "./url_search_params.ts"; import { fetch as opFetch, FetchResponse } from "../ops/fetch.ts"; +import { DomFileImpl } from "./dom_file.ts"; function getHeaderValueParams(value: string): Map<string, string> { const params = new Map(); @@ -499,8 +500,47 @@ export async function fetch( } else if (init.body instanceof DenoBlob) { body = init.body[blobBytesSymbol]; contentType = init.body.type; + } else if (init.body instanceof FormData) { + 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; + } + payload += `\r\n--${boundary}--`; + body = new TextEncoder().encode(payload); + contentType = "multipart/form-data; boundary=" + boundary; } else { - // TODO: FormData, ReadableStream + // TODO: ReadableStream notImplemented(); } if (contentType && !headers.has("content-type")) { diff --git a/cli/js/web/form_data.ts b/cli/js/web/form_data.ts index 2c70d416f..f60a146d9 100644 --- a/cli/js/web/form_data.ts +++ b/cli/js/web/form_data.ts @@ -16,7 +16,9 @@ class FormDataBase { requiredArguments("FormData.append", arguments.length, 2); name = String(name); if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name); + const dfile = new domFile.DomFileImpl([value], filename || name, { + type: value.type + }); this[dataSymbol].push([name, dfile]); } else { this[dataSymbol].push([name, String(value)]); @@ -81,7 +83,9 @@ class FormDataBase { if (this[dataSymbol][i][0] === name) { if (!found) { if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name); + const dfile = new domFile.DomFileImpl([value], filename || name, { + type: value.type + }); this[dataSymbol][i][1] = dfile; } else { this[dataSymbol][i][1] = String(value); @@ -98,7 +102,9 @@ class FormDataBase { // Otherwise, append entry to the context object’s entry list. if (!found) { if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name); + const dfile = new domFile.DomFileImpl([value], filename || name, { + type: value.type + }); this[dataSymbol].push([name, dfile]); } else { this[dataSymbol].push([name, String(value)]); |