1054 lines
29 KiB
Plaintext
1054 lines
29 KiB
Plaintext
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const { inspect } = require('util');
|
|
|
|
const busboy = require('..');
|
|
|
|
const active = new Map();
|
|
|
|
const tests = [
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'super alpha file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_1"',
|
|
'',
|
|
'super beta file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'A'.repeat(1023),
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="1k_b.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'B'.repeat(1023),
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'super alpha file',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'field',
|
|
name: 'file_name_1',
|
|
val: 'super beta file',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('A'.repeat(1023)),
|
|
info: {
|
|
filename: '1k_a.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_1',
|
|
data: Buffer.from('B'.repeat(1023)),
|
|
info: {
|
|
filename: '1k_b.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Fields and files'
|
|
},
|
|
{ source: [
|
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: form-data; name="cont"',
|
|
'',
|
|
'some random content',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: form-data; name="pass"',
|
|
'',
|
|
'some random pass',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: form-data; name=bit',
|
|
'',
|
|
'2',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'cont',
|
|
val: 'some random content',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'field',
|
|
name: 'pass',
|
|
val: 'some random pass',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'field',
|
|
name: 'bit',
|
|
val: '2',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
],
|
|
what: 'Fields only'
|
|
},
|
|
{ source: [
|
|
''
|
|
],
|
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
expected: [
|
|
{ error: 'Unexpected end of form' },
|
|
],
|
|
what: 'No fields and no files'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'super alpha file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
fileSize: 13,
|
|
fieldSize: 5
|
|
},
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'super',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: true,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('ABCDEFGHIJKLM'),
|
|
info: {
|
|
filename: '1k_a.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: true,
|
|
},
|
|
],
|
|
what: 'Fields and files (limits)'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'super alpha file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
files: 0
|
|
},
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'super alpha file',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
'filesLimit',
|
|
],
|
|
what: 'Fields and files (limits: 0 files)'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'super alpha file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_1"',
|
|
'',
|
|
'super beta file',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'A'.repeat(1023),
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="1k_b.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'B'.repeat(1023),
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'super alpha file',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
{ type: 'field',
|
|
name: 'file_name_1',
|
|
val: 'super beta file',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
],
|
|
events: ['field'],
|
|
what: 'Fields and (ignored) files'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="/tmp/1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="C:\\files\\1k_b.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_2"; filename="relative/1k_c.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: '1k_a.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_1',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: '1k_b.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_2',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: '1k_c.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Files with filenames containing paths'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="/absolute/1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_2"; filename="relative/1k_c.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
preservePath: true,
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: '/absolute/1k_a.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_1',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: 'C:\\absolute\\1k_b.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_2',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: 'relative/1k_c.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Paths to be preserved through the preservePath option'
|
|
},
|
|
{ source: [
|
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: form-data; name="cont"',
|
|
'Content-Type: ',
|
|
'',
|
|
'some random content',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: ',
|
|
'',
|
|
'some random pass',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'cont',
|
|
val: 'some random content',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
],
|
|
what: 'Empty content-type and empty content-disposition'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="file"; filename*=utf-8\'\'n%C3%A4me.txt',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'file',
|
|
data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
info: {
|
|
filename: 'näme.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Unicode filenames'
|
|
},
|
|
{ source: [
|
|
['--asdasdasdasd\r\n',
|
|
'Content-Type: text/plain\r\n',
|
|
'Content-Disposition: form-data; name="foo"\r\n',
|
|
'\r\n',
|
|
'asd\r\n',
|
|
'--asdasdasdasd--'
|
|
].join(':)')
|
|
],
|
|
boundary: 'asdasdasdasd',
|
|
expected: [
|
|
{ error: 'Malformed part header' },
|
|
{ error: 'Unexpected end of form' },
|
|
],
|
|
what: 'Stopped mid-header'
|
|
},
|
|
{ source: [
|
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
'Content-Disposition: form-data; name="cont"',
|
|
'Content-Type: application/json',
|
|
'',
|
|
'{}',
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'cont',
|
|
val: '{}',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'application/json',
|
|
},
|
|
},
|
|
],
|
|
what: 'content-type for fields'
|
|
},
|
|
{ source: [
|
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
|
|
],
|
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
|
|
expected: [],
|
|
what: 'empty form'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name=upload_file_0; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'Content-Transfer-Encoding: binary',
|
|
'',
|
|
'',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.alloc(0),
|
|
info: {
|
|
filename: '1k_a.dat',
|
|
encoding: 'binary',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
err: 'Unexpected end of form',
|
|
},
|
|
{ error: 'Unexpected end of form' },
|
|
],
|
|
what: 'Stopped mid-file #1'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name=upload_file_0; filename="1k_a.dat"',
|
|
'Content-Type: application/octet-stream',
|
|
'',
|
|
'a',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('a'),
|
|
info: {
|
|
filename: '1k_a.dat',
|
|
encoding: '7bit',
|
|
mimeType: 'application/octet-stream',
|
|
},
|
|
limited: false,
|
|
err: 'Unexpected end of form',
|
|
},
|
|
{ error: 'Unexpected end of form' },
|
|
],
|
|
what: 'Stopped mid-file #2'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'a',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('a'),
|
|
info: {
|
|
filename: 'notes.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Text file with charset'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: ',
|
|
' text/plain; charset=utf8',
|
|
'',
|
|
'a',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('a'),
|
|
info: {
|
|
filename: 'notes.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Folded header value'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'a',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [],
|
|
what: 'No Content-Disposition'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'a'.repeat(64 * 1024),
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: ',
|
|
' text/plain; charset=utf8',
|
|
'',
|
|
'bc',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
fieldSize: Infinity,
|
|
},
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('bc'),
|
|
info: {
|
|
filename: 'notes.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
events: [ 'file' ],
|
|
what: 'Skip field parts if no listener'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'a',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: ',
|
|
' text/plain; charset=utf8',
|
|
'',
|
|
'bc',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
parts: 1,
|
|
},
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'a',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
'partsLimit',
|
|
],
|
|
what: 'Parts limit'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_0"',
|
|
'',
|
|
'a',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; name="file_name_1"',
|
|
'',
|
|
'b',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
fields: 1,
|
|
},
|
|
expected: [
|
|
{ type: 'field',
|
|
name: 'file_name_0',
|
|
val: 'a',
|
|
info: {
|
|
nameTruncated: false,
|
|
valueTruncated: false,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
},
|
|
'fieldsLimit',
|
|
],
|
|
what: 'Fields limit'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'ab',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="notes2.txt"',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'cd',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
limits: {
|
|
files: 1,
|
|
},
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('ab'),
|
|
info: {
|
|
filename: 'notes.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
'filesLimit',
|
|
],
|
|
what: 'Files limit'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ `name="upload_file_0"; filename="${'a'.repeat(64 * 1024)}.txt"`,
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'ab',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_1"; filename="notes2.txt"',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'cd',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ error: 'Malformed part header' },
|
|
{ type: 'file',
|
|
name: 'upload_file_1',
|
|
data: Buffer.from('cd'),
|
|
info: {
|
|
filename: 'notes2.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Oversized part header'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ 'name="upload_file_0"; filename="notes.txt"',
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'a'.repeat(31) + '\r',
|
|
].join('\r\n'),
|
|
'b'.repeat(40),
|
|
'\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
fileHwm: 32,
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('a'.repeat(31) + '\r' + 'b'.repeat(40)),
|
|
info: {
|
|
filename: 'notes.txt',
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Lookbehind data should not stall file streams'
|
|
},
|
|
{ source: [
|
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ `name="upload_file_0"; filename="${'a'.repeat(8 * 1024)}.txt"`,
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'ab',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ `name="upload_file_1"; filename="${'b'.repeat(8 * 1024)}.txt"`,
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'cd',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
'Content-Disposition: form-data; '
|
|
+ `name="upload_file_2"; filename="${'c'.repeat(8 * 1024)}.txt"`,
|
|
'Content-Type: text/plain; charset=utf8',
|
|
'',
|
|
'ef',
|
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
|
|
].join('\r\n')
|
|
],
|
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'upload_file_0',
|
|
data: Buffer.from('ab'),
|
|
info: {
|
|
filename: `${'a'.repeat(8 * 1024)}.txt`,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_1',
|
|
data: Buffer.from('cd'),
|
|
info: {
|
|
filename: `${'b'.repeat(8 * 1024)}.txt`,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
{ type: 'file',
|
|
name: 'upload_file_2',
|
|
data: Buffer.from('ef'),
|
|
info: {
|
|
filename: `${'c'.repeat(8 * 1024)}.txt`,
|
|
encoding: '7bit',
|
|
mimeType: 'text/plain',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Header size limit should be per part'
|
|
},
|
|
{ source: [
|
|
'\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee\r\n',
|
|
'Content-Type: application/gzip\r\n'
|
|
+ 'Content-Encoding: gzip\r\n'
|
|
+ 'Content-Disposition: form-data; name=batch-1; filename=batch-1'
|
|
+ '\r\n\r\n',
|
|
'\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee--',
|
|
],
|
|
boundary: 'd1bf46b3-aa33-4061-b28d-6c5ced8b08ee',
|
|
expected: [
|
|
{ type: 'file',
|
|
name: 'batch-1',
|
|
data: Buffer.alloc(0),
|
|
info: {
|
|
filename: 'batch-1',
|
|
encoding: '7bit',
|
|
mimeType: 'application/gzip',
|
|
},
|
|
limited: false,
|
|
},
|
|
],
|
|
what: 'Empty part'
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
active.set(test, 1);
|
|
|
|
const { what, boundary, events, limits, preservePath, fileHwm } = test;
|
|
const bb = busboy({
|
|
fileHwm,
|
|
limits,
|
|
preservePath,
|
|
headers: {
|
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
}
|
|
});
|
|
const results = [];
|
|
|
|
if (events === undefined || events.includes('field')) {
|
|
bb.on('field', (name, val, info) => {
|
|
results.push({ type: 'field', name, val, info });
|
|
});
|
|
}
|
|
|
|
if (events === undefined || events.includes('file')) {
|
|
bb.on('file', (name, stream, info) => {
|
|
const data = [];
|
|
let nb = 0;
|
|
const file = {
|
|
type: 'file',
|
|
name,
|
|
data: null,
|
|
info,
|
|
limited: false,
|
|
};
|
|
results.push(file);
|
|
stream.on('data', (d) => {
|
|
data.push(d);
|
|
nb += d.length;
|
|
}).on('limit', () => {
|
|
file.limited = true;
|
|
}).on('close', () => {
|
|
file.data = Buffer.concat(data, nb);
|
|
assert.strictEqual(stream.truncated, file.limited);
|
|
}).once('error', (err) => {
|
|
file.err = err.message;
|
|
});
|
|
});
|
|
}
|
|
|
|
bb.on('error', (err) => {
|
|
results.push({ error: err.message });
|
|
});
|
|
|
|
bb.on('partsLimit', () => {
|
|
results.push('partsLimit');
|
|
});
|
|
|
|
bb.on('filesLimit', () => {
|
|
results.push('filesLimit');
|
|
});
|
|
|
|
bb.on('fieldsLimit', () => {
|
|
results.push('fieldsLimit');
|
|
});
|
|
|
|
bb.on('close', () => {
|
|
active.delete(test);
|
|
|
|
assert.deepStrictEqual(
|
|
results,
|
|
test.expected,
|
|
`[${what}] Results mismatch.\n`
|
|
+ `Parsed: ${inspect(results)}\n`
|
|
+ `Expected: ${inspect(test.expected)}`
|
|
);
|
|
});
|
|
|
|
for (const src of test.source) {
|
|
const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
|
|
bb.write(buf);
|
|
}
|
|
bb.end();
|
|
}
|
|
|
|
// Byte-by-byte versions
|
|
for (let test of tests) {
|
|
test = { ...test };
|
|
test.what += ' (byte-by-byte)';
|
|
active.set(test, 1);
|
|
|
|
const { what, boundary, events, limits, preservePath, fileHwm } = test;
|
|
const bb = busboy({
|
|
fileHwm,
|
|
limits,
|
|
preservePath,
|
|
headers: {
|
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
}
|
|
});
|
|
const results = [];
|
|
|
|
if (events === undefined || events.includes('field')) {
|
|
bb.on('field', (name, val, info) => {
|
|
results.push({ type: 'field', name, val, info });
|
|
});
|
|
}
|
|
|
|
if (events === undefined || events.includes('file')) {
|
|
bb.on('file', (name, stream, info) => {
|
|
const data = [];
|
|
let nb = 0;
|
|
const file = {
|
|
type: 'file',
|
|
name,
|
|
data: null,
|
|
info,
|
|
limited: false,
|
|
};
|
|
results.push(file);
|
|
stream.on('data', (d) => {
|
|
data.push(d);
|
|
nb += d.length;
|
|
}).on('limit', () => {
|
|
file.limited = true;
|
|
}).on('close', () => {
|
|
file.data = Buffer.concat(data, nb);
|
|
assert.strictEqual(stream.truncated, file.limited);
|
|
}).once('error', (err) => {
|
|
file.err = err.message;
|
|
});
|
|
});
|
|
}
|
|
|
|
bb.on('error', (err) => {
|
|
results.push({ error: err.message });
|
|
});
|
|
|
|
bb.on('partsLimit', () => {
|
|
results.push('partsLimit');
|
|
});
|
|
|
|
bb.on('filesLimit', () => {
|
|
results.push('filesLimit');
|
|
});
|
|
|
|
bb.on('fieldsLimit', () => {
|
|
results.push('fieldsLimit');
|
|
});
|
|
|
|
bb.on('close', () => {
|
|
active.delete(test);
|
|
|
|
assert.deepStrictEqual(
|
|
results,
|
|
test.expected,
|
|
`[${what}] Results mismatch.\n`
|
|
+ `Parsed: ${inspect(results)}\n`
|
|
+ `Expected: ${inspect(test.expected)}`
|
|
);
|
|
});
|
|
|
|
for (const src of test.source) {
|
|
const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
|
|
for (let i = 0; i < buf.length; ++i)
|
|
bb.write(buf.slice(i, i + 1));
|
|
}
|
|
bb.end();
|
|
}
|
|
|
|
{
|
|
let exception = false;
|
|
process.once('uncaughtException', (ex) => {
|
|
exception = true;
|
|
throw ex;
|
|
});
|
|
process.on('exit', () => {
|
|
if (exception || active.size === 0)
|
|
return;
|
|
process.exitCode = 1;
|
|
console.error('==========================');
|
|
console.error(`${active.size} test(s) did not finish:`);
|
|
console.error('==========================');
|
|
console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
|
|
});
|
|
}
|