Skip to content

Commit

Permalink
feat: fetch should accept a FormData body (denoland#4363)
Browse files Browse the repository at this point in the history
  • Loading branch information
crowlKats authored Mar 17, 2020
1 parent f9557a4 commit 9833975
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 4 deletions.
14 changes: 14 additions & 0 deletions cli/js/tests/fetch_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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("https://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
> {
Expand Down
42 changes: 41 additions & 1 deletion cli/js/web/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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")) {
Expand Down
12 changes: 9 additions & 3 deletions cli/js/web/form_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)]);
Expand Down Expand Up @@ -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);
Expand All @@ -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)]);
Expand Down

0 comments on commit 9833975

Please sign in to comment.