summaryrefslogtreecommitdiff
path: root/cli/js
diff options
context:
space:
mode:
authorcrowlKats <crowlkats@gmail.com>2020-03-17 07:32:43 +0100
committerGitHub <noreply@github.com>2020-03-17 02:32:43 -0400
commit9833975ef21afced84c6a16724ce13d026f09298 (patch)
tree1a9d76a9bd12c70d362d6feafc37515c66c446c0 /cli/js
parentf9557a4ff6b73a4af37e713bb6b2294253c7b230 (diff)
feat: fetch should accept a FormData body (#4363)
Diffstat (limited to 'cli/js')
-rw-r--r--cli/js/tests/fetch_test.ts14
-rw-r--r--cli/js/web/fetch.ts42
-rw-r--r--cli/js/web/form_data.ts12
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)]);