Skip to content

Commit 32b1bfd

Browse files
committed
Add option for max parts
1 parent cdf6c0b commit 32b1bfd

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

lib/index.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ internals.state = {
4949

5050

5151
internals.defaults = {
52-
maxBytes: Infinity
52+
maxBytes: Infinity,
53+
maxParts: Infinity
5354
};
5455

5556

@@ -71,8 +72,10 @@ exports.Dispenser = class extends Stream.Writable {
7172
this._name = '';
7273
this._pendingHeader = '';
7374
this._error = null;
74-
this._bytes = 0;
75+
this._bytesCount = 0;
76+
this._partsCount = 0;
7577
this._maxBytes = settings.maxBytes;
78+
this._maxParts = settings.maxParts;
7679

7780
this._parts = new Nigel.Stream(Buffer.from('--' + settings.boundary));
7881
this._lines = new Nigel.Stream(Buffer.from('\r\n'));
@@ -132,9 +135,9 @@ exports.Dispenser = class extends Stream.Writable {
132135

133136
const onReqData = (data) => {
134137

135-
this._bytes += Buffer.byteLength(data);
138+
this._bytesCount += Buffer.byteLength(data);
136139

137-
if (this._bytes > this._maxBytes) {
140+
if (this._bytesCount > this._maxBytes) {
138141
finish(Boom.entityTooLarge('Maximum size exceeded'));
139142
}
140143
};
@@ -193,6 +196,12 @@ exports.Dispenser = class extends Stream.Writable {
193196

194197
this._parts.needle(Buffer.from('\r\n--' + this._boundary)); // CRLF no longer optional
195198
}
199+
else {
200+
this._partsCount++;
201+
if (this._partsCount > this._maxParts) {
202+
return this.#abort(Boom.badRequest('Maximum parts exceeded'));
203+
}
204+
}
196205

197206
this._state = internals.state.boundary;
198207

test/index.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,52 @@ describe('Dispenser', () => {
543543
await team.work;
544544
});
545545

546+
it('errors if the payload size exceeds the part limit', async () => {
547+
548+
const createParts = (n) => {
549+
550+
return [
551+
'--AaB03x\r\n',
552+
`content-disposition: form-data; name="field${n}"\r\n`,
553+
'\r\n',
554+
'one\r\ntwo\r\n',
555+
'--AaB03x\r\n',
556+
`content-disposition: form-data; name="file${n}"; filename="file${n}.txt"\r\n`,
557+
'Content-Type: text/plain\r\n',
558+
'\r\n',
559+
'hello world\r\n'
560+
].join('');
561+
};
562+
563+
const endParts = () => '--AaB03x--\r\n';
564+
565+
const payload = [
566+
createParts(2),
567+
createParts(4),
568+
createParts(6),
569+
createParts(8),
570+
createParts(10),
571+
endParts()
572+
].join('');
573+
574+
const req = new internals.Payload(payload, true);
575+
req.headers = { 'content-type': 'multipart/form-data; boundary="AaB03x"' };
576+
577+
const dispenser = new Pez.Dispenser({ boundary: 'AaB03x', maxParts: 9 });
578+
579+
const team = new Teamwork.Team();
580+
dispenser.once('error', (err) => {
581+
582+
expect(err).to.exist();
583+
expect(err.message).to.equal('Maximum parts exceeded');
584+
expect(err.output.statusCode).to.equal(400);
585+
team.attend();
586+
});
587+
588+
req.pipe(dispenser);
589+
await team.work;
590+
});
591+
546592
it('parses a request with "=" in the boundary', async () => {
547593

548594
const payload =

0 commit comments

Comments
 (0)