Skip to content

Commit 2775e68

Browse files
Networking: Preserve the content-type header when fetch()-ing (#2028)
Allows the use of arbitrary Content-type header when sending POST requests from WordPress. Playground fetch() network transport for `wp_safe_remote_*` calls enforced the `Content-Type` header of `application/x-www-form-urlencoding` when POST-ing. This PR turns that value into a fallback that's only used if not explicit value is provided. Closes #1428 ## Testing instructions * CI unit tests * Try the Blueprint URL below and confirm in devtools the content-type header used was `text/plain` ``` http://localhost:5400/website-server/#{%22features%22:{%22networking%22:true},%22steps%22:[{%22step%22:%22writeFile%22,%22path%22:%22/wordpress/wp-content/mu-plugins/0-fetch-test.php%22,%22data%22:%22%3C?php%20add_action('init',%20function()%20{%20wp_safe_remote_post('https://api.w.org',array('headers'=%3E['Content-Type'=%3E'text/plain']));%20});%20%22}]} ``` --------- Co-authored-by: Brandon Payton <[email protected]>
1 parent 87bff2a commit 2775e68

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

packages/playground/remote/src/lib/setup-fetch-network-transport.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,12 @@ export async function handleRequest(data: RequestData, fetchFn = fetch) {
7272
try {
7373
const fetchMethod = data.method || 'GET';
7474
const fetchHeaders = data.headers || {};
75-
if (fetchMethod == 'POST') {
75+
76+
const hasContentTypeHeader = Object.keys(fetchHeaders).some(
77+
(name) => name.toLowerCase() === "content-type"
78+
);
79+
80+
if (fetchMethod == 'POST' && !hasContentTypeHeader) {
7681
fetchHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
7782
}
7883

packages/playground/remote/src/test/setup-fetch-network-transport.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,36 @@ describe('handleRequest', () => {
2525
`HTTP/1.1 200 OK\r\ncontent-type: text/html\r\n\r\nHello, world!`
2626
);
2727
});
28+
it('Should preserve the Content-Type header when POSTing', async () => {
29+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
30+
const fetchMock = vitest.fn(async (u: string, o: RequestInit) => {
31+
return {
32+
status: 200,
33+
statusText: 'OK',
34+
headers: new Headers({
35+
'Content-type': 'text/html',
36+
}),
37+
arrayBuffer: async () => {
38+
return new TextEncoder().encode('Hello, world!');
39+
},
40+
};
41+
});
42+
const response = await handleRequest(
43+
{
44+
url: 'https://playground.wordpress.net/',
45+
headers: { 'Content-type': 'text/html' },
46+
method: 'POST',
47+
},
48+
fetchMock as any
49+
);
50+
const headers = fetchMock.mock.calls[0][1]?.headers || {};
51+
expect(headers).toEqual({
52+
'Content-type': 'text/html',
53+
});
54+
expect(new TextDecoder().decode(response)).toBe(
55+
`HTTP/1.1 200 OK\r\ncontent-type: text/html\r\n\r\nHello, world!`
56+
);
57+
});
2858
it('Should reject responses with malicious headers trying to terminate the headers section early', async () => {
2959
const fetchMock = vitest.fn(async () => {
3060
return {

0 commit comments

Comments
 (0)