Skip to content

Commit 0bb4410

Browse files
authored
fix: handle multipart/form-data submissions (#8984)
* fix: handle multipart/form-data submissions * add changeset
1 parent e6b6811 commit 0bb4410

File tree

4 files changed

+45
-6
lines changed

4 files changed

+45
-6
lines changed

.changeset/brave-shirts-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
fix: properly handle `<Form encType="multipart/form-data">` submissions (#8984)

packages/router/__tests__/router-test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3868,7 +3868,7 @@ describe("a router", () => {
38683868
expect(request.url).toBe("http://localhost/tasks");
38693869
expect(request.method).toBe("POST");
38703870
expect(request.headers.get("Content-Type")).toBe(
3871-
"application/x-www-form-urlencoded"
3871+
"application/x-www-form-urlencoded;charset=UTF-8"
38723872
);
38733873
expect((await request.formData()).get("query")).toBe("params");
38743874
});
@@ -3901,10 +3901,44 @@ describe("a router", () => {
39013901
expect(request.url).toBe("http://localhost/tasks?foo=bar");
39023902
expect(request.method).toBe("POST");
39033903
expect(request.headers.get("Content-Type")).toBe(
3904-
"application/x-www-form-urlencoded"
3904+
"application/x-www-form-urlencoded;charset=UTF-8"
39053905
);
39063906
expect((await request.formData()).get("query")).toBe("params");
39073907
});
3908+
3909+
it("handles multipart/form-data submissions", async () => {
3910+
let t = setup({
3911+
routes: [
3912+
{
3913+
id: "root",
3914+
path: "/",
3915+
action: true,
3916+
},
3917+
],
3918+
initialEntries: ["/"],
3919+
hydrationData: {
3920+
loaderData: {
3921+
root: "ROOT_DATA",
3922+
},
3923+
},
3924+
});
3925+
3926+
let fd = new FormData();
3927+
fd.append("key", "value");
3928+
fd.append("file", new Blob(["1", "2", "3"]), "file.txt");
3929+
3930+
let A = await t.navigate("/", {
3931+
formMethod: "post",
3932+
formEncType: "multipart/form-data",
3933+
formData: fd,
3934+
});
3935+
3936+
expect(
3937+
A.actions.root.stub.mock.calls[0][0].request.headers.get("Content-Type")
3938+
).toMatch(
3939+
/^multipart\/form-data; boundary=NodeFetchFormDataBoundary[a-z0-9]+/
3940+
);
3941+
});
39083942
});
39093943

39103944
describe("scroll restoration", () => {

packages/router/router.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,11 +1846,9 @@ function createRequest(
18461846
}
18471847
}
18481848

1849+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
18491850
return new Request(url, {
18501851
method: formMethod.toUpperCase(),
1851-
headers: {
1852-
"Content-Type": formEncType,
1853-
},
18541852
body,
18551853
});
18561854
}

packages/router/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { parsePath } from "./history";
33
import { DataResult, DataRouteMatch } from "./router";
44

55
export type FormMethod = "get" | "post" | "put" | "patch" | "delete";
6-
export type FormEncType = "application/x-www-form-urlencoded";
6+
export type FormEncType =
7+
| "application/x-www-form-urlencoded"
8+
| "multipart/form-data";
79

810
/**
911
* @private

0 commit comments

Comments
 (0)