Skip to content

Commit fdafc2a

Browse files
authored
Formdata ignore epilogue preamble (#4672)
1 parent fbfac48 commit fdafc2a

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed

lib/web/fetch/formdata-parser.js

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ const { isomorphicDecode } = require('../infra')
99
const { utf8DecodeBytes } = require('../../encoding')
1010

1111
const dd = Buffer.from('--')
12-
const ddcrlf = Buffer.from('--\r\n')
13-
1412
const decoder = new TextDecoder()
1513

1614
/**
@@ -85,20 +83,16 @@ function multipartFormDataParser (input, mimeType) {
8583
// the first byte.
8684
const position = { position: 0 }
8785

88-
// Note: undici addition, allows leading and trailing CRLFs.
89-
while (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
90-
position.position += 2
91-
}
92-
93-
let trailing = input.length
86+
// Note: Per RFC 2046 Section 5.1.1, we must ignore anything before the
87+
// first boundary delimiter line (preamble). Search for the first boundary.
88+
const firstBoundaryIndex = input.indexOf(boundary)
9489

95-
while (input[trailing - 1] === 0x0a && input[trailing - 2] === 0x0d) {
96-
trailing -= 2
90+
if (firstBoundaryIndex === -1) {
91+
throw parsingError('no boundary found in multipart body')
9792
}
9893

99-
if (trailing !== input.length) {
100-
input = input.subarray(0, trailing)
101-
}
94+
// Start parsing from the first boundary, ignoring any preamble
95+
position.position = firstBoundaryIndex
10296

10397
// 5. While true:
10498
while (true) {
@@ -114,11 +108,11 @@ function multipartFormDataParser (input, mimeType) {
114108

115109
// 5.2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A
116110
// (`--` followed by CR LF) followed by the end of input, return entry list.
117-
// Note: a body does NOT need to end with CRLF. It can end with --.
118-
if (
119-
(position.position === input.length - 2 && bufferStartsWith(input, dd, position)) ||
120-
(position.position === input.length - 4 && bufferStartsWith(input, ddcrlf, position))
121-
) {
111+
// Note: Per RFC 2046 Section 5.1.1, we must ignore anything after the
112+
// final boundary delimiter (epilogue). Check for -- or --CRLF and return
113+
// regardless of what follows.
114+
if (bufferStartsWith(input, dd, position)) {
115+
// Found closing boundary delimiter (--), ignore any epilogue
122116
return entryList
123117
}
124118

test/busboy/issue-4671.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict'
2+
3+
const { test } = require('node:test')
4+
const { Request, Response, FormData } = require('../..')
5+
6+
// https://github.com/nodejs/undici/issues/4671
7+
test('preamble and epilogue is ignored', async (t) => {
8+
const request = new Request('https://example.com', {
9+
method: 'POST',
10+
body: (function () {
11+
const formData = new FormData()
12+
formData.append('a', 'b')
13+
return formData
14+
})()
15+
})
16+
17+
const contentType = request.headers.get('Content-Type')
18+
let bytes = await request.bytes()
19+
bytes = new Uint8Array([...bytes, ...Array(10).fill(0)])
20+
21+
await t.test('epilogue', async () => {
22+
await new Response(bytes, {
23+
headers: {
24+
'Content-Type': contentType
25+
}
26+
}).formData()
27+
})
28+
29+
await t.test('preamble', async () => {
30+
// preamble
31+
bytes.set(bytes.subarray(0, -10), 10)
32+
bytes.fill(0, 0, 8)
33+
bytes[8] = 13
34+
bytes[9] = 10
35+
36+
await new Response(bytes, {
37+
headers: {
38+
'Content-Type': contentType
39+
}
40+
}).formData()
41+
})
42+
})

0 commit comments

Comments
 (0)