Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions lib/types/multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ class Multipart extends Writable {
const partsLimit = (limits && typeof limits.parts === 'number'
? limits.parts
: Infinity);
const fieldNameSizeLimit = (limits
&& typeof limits.fieldNameSize === 'number'
? limits.fieldNameSize
: Infinity);

let parts = -1; // Account for initial boundary
let fields = 0;
Expand All @@ -280,6 +284,7 @@ class Multipart extends Writable {
let partEncoding;
let partType;
let partName;
let partNameTruncated = false;
let partTruncated = false;

let hitFilesLimit = false;
Expand All @@ -294,6 +299,7 @@ class Multipart extends Writable {
partCharset = defCharset;
partEncoding = '7bit';
partName = undefined;
partNameTruncated = false;
partTruncated = false;

let filename;
Expand All @@ -303,15 +309,20 @@ class Multipart extends Writable {
}

const disp = parseDisposition(header['content-disposition'][0],
paramDecoder);
paramDecoder, fieldNameSizeLimit);
if (!disp || disp.type !== 'form-data') {
skipPart = true;
return;
}

if (disp.params) {
if (disp.params.name)
if (disp.params.name) {
// fieldNameSize limit is enforced at parse time by parseDisposition
// (the value is already truncated and disp.nameTruncated is set).
partName = disp.params.name;
if (disp.nameTruncated)
partNameTruncated = true;
}

if (disp.params['filename*'])
filename = disp.params['filename*'];
Expand Down Expand Up @@ -538,7 +549,7 @@ retrydata:
'field',
partName,
data,
{ nameTruncated: false,
{ nameTruncated: partNameTruncated,
valueTruncated: partTruncated,
encoding: partEncoding,
mimeType: partType }
Expand Down
25 changes: 20 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,28 +160,35 @@ function parseContentTypeParams(str, i, params) {
return params;
}

function parseDisposition(str, defDecoder) {
function parseDisposition(str, defDecoder, fieldNameSize) {
if (str.length === 0)
return;

const params = Object.create(null);
let i = 0;
let nameTruncated = false;
const nameByteLimit = (typeof fieldNameSize === 'number'
? fieldNameSize
: Infinity);

for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
if (parseDispositionParams(str, i, params, defDecoder) === undefined)
if (parseDispositionParams(str, i, params, defDecoder, nameByteLimit)
=== undefined)
return;
nameTruncated = params._nameTruncated === true;
delete params._nameTruncated;
break;
}
}

const type = str.slice(0, i).toLowerCase();

return { type, params };
return { type, params, nameTruncated };
}

function parseDispositionParams(str, i, params, defDecoder) {
function parseDispositionParams(str, i, params, defDecoder, nameByteLimit) {
while (i < str.length) {
// Consume whitespace
for (; i < str.length; ++i) {
Expand Down Expand Up @@ -374,8 +381,16 @@ function parseDispositionParams(str, i, params, defDecoder) {
}

name = name.toLowerCase();
if (params[name] === undefined)
if (params[name] === undefined) {
// Enforce fieldNameSize limit on the `name` parameter value at parse time
// (avoid allocating the full value string when over the limit).
if (name === 'name' && nameByteLimit !== Infinity
&& value.length > nameByteLimit) {
value = value.slice(0, nameByteLimit);
params._nameTruncated = true;
}
params[name] = value;
}
}

return params;
Expand Down
40 changes: 40 additions & 0 deletions test/test-types-multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,46 @@ const tests = [
],
what: 'Empty part'
},
{ 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',
'',
'A'.repeat(1023),
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
expected: [
{ type: 'field',
name: 'file',
val: 'super alpha file',
info: {
nameTruncated: true,
valueTruncated: false,
encoding: '7bit',
mimeType: 'text/plain',
},
},
{ type: 'file',
name: 'uplo',
data: Buffer.from('A'.repeat(1023)),
info: {
filename: '1k_a.dat',
encoding: '7bit',
mimeType: 'application/octet-stream',
},
limited: false,
},
],
limits: { fieldNameSize: 4 },
what: 'Limits: truncated field name (multipart)'
},
];

for (const test of tests) {
Expand Down