Skip to content

Commit 7298524

Browse files
authored
add busboy tests (#2924)
1 parent be8ef8e commit 7298524

File tree

5 files changed

+742
-4
lines changed

5 files changed

+742
-4
lines changed

lib/web/fetch/formdata-parser.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
66
const { isFileLike, File: UndiciFile } = require('./file')
77
const { makeEntry } = require('./formdata')
88
const assert = require('node:assert')
9-
const { isAscii } = require('node:buffer')
9+
const { isAscii, File: NodeFile } = require('node:buffer')
1010

11-
const File = globalThis.File ?? UndiciFile
11+
const File = globalThis.File ?? NodeFile ?? UndiciFile
1212

1313
const formDataNameBuffer = Buffer.from('form-data; name="')
14-
const filenameBuffer = Buffer.from('; filename="')
14+
const filenameBuffer = Buffer.from('; filename')
1515
const dd = Buffer.from('--')
1616
const ddcrlf = Buffer.from('--\r\n')
1717

@@ -110,6 +110,11 @@ function multipartFormDataParser (input, mimeType) {
110110
// the first byte.
111111
const position = { position: 0 }
112112

113+
// Note: undici addition, allow \r\n before the body.
114+
if (input[0] === 0x0d && input[1] === 0x0a) {
115+
position.position += 2
116+
}
117+
113118
// 5. While true:
114119
while (true) {
115120
// 5.1. If position points to a sequence of bytes starting with 0x2D 0x2D
@@ -301,6 +306,18 @@ function parseMultipartFormDataHeaders (input, position) {
301306

302307
// 5. If position points to a sequence of bytes starting with `; filename="`:
303308
if (bufferStartsWith(input, filenameBuffer, position)) {
309+
// Note: undici also handles filename*
310+
let check = position.position + filenameBuffer.length
311+
312+
if (input[check] === 0x2a) {
313+
position.position += 1
314+
check += 1
315+
}
316+
317+
if (input[check] !== 0x3d || input[check + 1] !== 0x22) { // ="
318+
return 'failure'
319+
}
320+
304321
// 1. Advance position so it points at the byte after the next 0x22 (") byte
305322
// (the one in the sequence of bytes matched above).
306323
position.position += 12

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"test:cookies": "borp -p \"test/cookie/*.js\"",
7373
"test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"",
7474
"test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
75-
"test:fetch": "npm run build:node && borp --expose-gc -p \"test/fetch/*.js\" && borp -p \"test/webidl/*.js\"",
75+
"test:fetch": "npm run build:node && borp --expose-gc -p \"test/fetch/*.js\" && borp -p \"test/webidl/*.js\" && borp -p \"test/busboy/*.js\"",
7676
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
7777
"test:unit": "borp --expose-gc -p \"test/*.js\"",
7878
"test:node-test": "borp -p \"test/node-test/**/*.js\"",

test/busboy/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright Brian White. All rights reserved.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to
5+
deal in the Software without restriction, including without limitation the
6+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
sell copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
IN THE SOFTWARE.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict'
2+
3+
const assert = require('node:assert')
4+
const { inspect } = require('node:util')
5+
const { test } = require('node:test')
6+
const { Response } = require('../..')
7+
8+
const input = Buffer.from([
9+
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
10+
'Content-Disposition: form-data; ' +
11+
'name="upload_file_0"; filename="テスト.dat"',
12+
'Content-Type: application/octet-stream',
13+
'',
14+
'A'.repeat(1023),
15+
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
16+
].join('\r\n'))
17+
const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'
18+
const expected = [
19+
{
20+
type: 'file',
21+
name: 'upload_file_0',
22+
data: Buffer.from('A'.repeat(1023)),
23+
info: {
24+
filename: 'テスト.dat',
25+
encoding: '7bit',
26+
mimeType: 'application/octet-stream'
27+
}
28+
}
29+
]
30+
31+
test('unicode filename', async (t) => {
32+
const response = new Response(input, {
33+
headers: {
34+
'content-type': `multipart/form-data; boundary=${boundary}`
35+
}
36+
})
37+
38+
const fd = await response.formData()
39+
const results = []
40+
41+
for (const [name, value] of fd) {
42+
if (typeof value === 'string') { // field
43+
results.push({
44+
type: 'field',
45+
name,
46+
val: value,
47+
info: {
48+
encoding: '7bit',
49+
mimeType: 'text/plain'
50+
}
51+
})
52+
} else { // File
53+
results.push({
54+
type: 'file',
55+
name,
56+
data: Buffer.from(await value.arrayBuffer()),
57+
info: {
58+
filename: value.name,
59+
encoding: '7bit',
60+
mimeType: value.type
61+
}
62+
})
63+
}
64+
}
65+
66+
assert.deepStrictEqual(
67+
results,
68+
expected,
69+
'Results mismatch.\n' +
70+
`Parsed: ${inspect(results)}\n` +
71+
`Expected: ${inspect(expected)}`
72+
)
73+
})

0 commit comments

Comments
 (0)