diff --git a/packages/node/src/body.ts b/packages/node/src/body.ts index 4c95610..c1d2ba0 100644 --- a/packages/node/src/body.ts +++ b/packages/node/src/body.ts @@ -153,6 +153,7 @@ export function toNodeHttpBody( if (body instanceof FormData) { const response = new Response(body) + // Read before the body: Bun drops the header once a body containing a File is consumed headers['content-type'] = response.headers.get('content-type')! return [Readable.fromWeb(response.body!), headers] } diff --git a/packages/peer/src/body.test.ts b/packages/peer/src/body.test.ts index 3449e1d..e8c73e8 100644 --- a/packages/peer/src/body.test.ts +++ b/packages/peer/src/body.test.ts @@ -382,6 +382,48 @@ describe('encodeAtomicStandardBody', () => { expect(binary).toBeInstanceOf(Blob) }) + it('keeps the case of a mixed-case FormData boundary', async () => { + const boundary = '----WebKitFormBoundaryQLr0YLsTkVq7MA89' + const OriginalResponse = Response + vi.stubGlobal( + 'Response', + class extends OriginalResponse { + constructor(...args: ConstructorParameters) { + if (args[0] instanceof FormData) { + super( + `--${boundary}\r\nContent-Disposition: form-data; name="key"\r\n\r\nvalue\r\n--${boundary}--\r\n`, + { headers: { 'content-type': `multipart/form-data; boundary=${boundary}` } }, + ) + } else { + super(...args) + } + } + }, + ) + onTestFinished(() => { + vi.unstubAllGlobals() + }) + + const form = new FormData() + form.append('key', 'value') + const encoded = await encodeAtomicStandardBody(form, {}) + + expect(encoded.headers['content-type']).toBe(`multipart/form-data; boundary=${boundary}`) + + const { resolveBody } = toStandardBody( + { + id: '1', + kind: 'request', + json: { url: '/upload', headers: encoded.headers, body: encoded.jsonBody }, + binary: encoded.binary, + }, + vi.fn(), + ) + + const received = (await resolveBody()) as FormData + expect(received.get('key')).toBe('value') + }) + it('encodes Blob body', async () => { const blob = new Blob(['data'], { type: 'text/plain' }) const { jsonBody, headers, binary } = await encodeAtomicStandardBody(blob, {}) diff --git a/packages/peer/src/body.ts b/packages/peer/src/body.ts index 311e3a9..3c47dd3 100644 --- a/packages/peer/src/body.ts +++ b/packages/peer/src/body.ts @@ -185,11 +185,12 @@ export async function encodeAtomicStandardBody( if (body instanceof FormData) { const res = new Response(body) + // Read before the body: Bun drops the header once a body containing a File is consumed + headers['content-type'] = res.headers.get('content-type')! const blob = await res.blob() // standard-server used to distinguish file vs. form-data headers['standard-server'] = 'form-data' satisfies StandardBodyHint - headers['content-type'] = blob.type headers['content-length'] = blob.size.toString() return { jsonBody: undefined, headers, binary: blob }