Skip to content

Commit 1699ec6

Browse files
authored
fix: flush or fail always (don't hang) (#945)
* fix: flush or fail always (don't hang) * feat: more descriptive errors * tests: make test work when other tests are run * tests: add test to confirm issue
1 parent 489a0bb commit 1699ec6

File tree

5 files changed

+62
-17
lines changed

5 files changed

+62
-17
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
### 3.5.1
4+
5+
* fix: ([#945](https://github.com/node-formidable/formidable/pull/945)) multipart parser fix: flush or fail always (don't hang)
6+
7+
38
### 3.5.0
49

510
* feature: ([#944](https://github.com/node-formidable/formidable/pull/944)) Dual package: Can be imported as ES module and required as commonjs module

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "formidable",
3-
"version": "3.5.0",
3+
"version": "3.5.1",
44
"license": "MIT",
55
"description": "A node.js module for parsing form data, especially file uploads.",
66
"homepage": "https://github.com/node-formidable/formidable",

src/parsers/Multipart.js

+27-15
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ class MultipartParser extends Transform {
5959
this.flags = 0;
6060
}
6161

62+
_endUnexpected() {
63+
return new FormidableError(
64+
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
65+
errors.malformedMultipart,
66+
400,
67+
);
68+
}
69+
6270
_flush(done) {
6371
if (
6472
(this.state === STATE.HEADER_FIELD_START && this.index === 0) ||
@@ -68,13 +76,9 @@ class MultipartParser extends Transform {
6876
this._handleCallback('end');
6977
done();
7078
} else if (this.state !== STATE.END) {
71-
done(
72-
new FormidableError(
73-
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
74-
errors.malformedMultipart,
75-
400,
76-
),
77-
);
79+
done(this._endUnexpected());
80+
} else {
81+
done();
7882
}
7983
}
8084

@@ -136,7 +140,8 @@ class MultipartParser extends Transform {
136140
c = buffer[i];
137141
switch (state) {
138142
case STATE.PARSER_UNINITIALIZED:
139-
return i;
143+
done(this._endUnexpected());
144+
return;
140145
case STATE.START:
141146
index = 0;
142147
state = STATE.START_BOUNDARY;
@@ -145,7 +150,8 @@ class MultipartParser extends Transform {
145150
if (c === HYPHEN) {
146151
flags |= FBOUNDARY.LAST_BOUNDARY;
147152
} else if (c !== CR) {
148-
return i;
153+
done(this._endUnexpected());
154+
return;
149155
}
150156
index++;
151157
break;
@@ -159,7 +165,8 @@ class MultipartParser extends Transform {
159165
this._handleCallback('partBegin');
160166
state = STATE.HEADER_FIELD_START;
161167
} else {
162-
return i;
168+
done(this._endUnexpected());
169+
return;
163170
}
164171
break;
165172
}
@@ -190,7 +197,8 @@ class MultipartParser extends Transform {
190197
if (c === COLON) {
191198
if (index === 1) {
192199
// empty header field
193-
return i;
200+
done(this._endUnexpected());
201+
return;
194202
}
195203
dataCallback('headerField', true);
196204
state = STATE.HEADER_VALUE_START;
@@ -199,7 +207,8 @@ class MultipartParser extends Transform {
199207

200208
cl = lower(c);
201209
if (cl < A || cl > Z) {
202-
return i;
210+
done(this._endUnexpected());
211+
return;
203212
}
204213
break;
205214
case STATE.HEADER_VALUE_START:
@@ -218,13 +227,15 @@ class MultipartParser extends Transform {
218227
break;
219228
case STATE.HEADER_VALUE_ALMOST_DONE:
220229
if (c !== LF) {
221-
return i;
230+
done(this._endUnexpected());
231+
return;
222232
}
223233
state = STATE.HEADER_FIELD_START;
224234
break;
225235
case STATE.HEADERS_ALMOST_DONE:
226236
if (c !== LF) {
227-
return i;
237+
done(this._endUnexpected());
238+
return;
228239
}
229240

230241
this._handleCallback('headersEnd');
@@ -311,7 +322,8 @@ class MultipartParser extends Transform {
311322
case STATE.END:
312323
break;
313324
default:
314-
return i;
325+
done(this._endUnexpected());
326+
return;
315327
}
316328
}
317329

test-node/standalone/createDirsFromUploads.test.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ d\r
5252
},
5353
body
5454
}).then(res => {
55-
deepEqual(fs.readdirSync(uploads), ['x']);
55+
//may also contain tests from other tests
56+
deepEqual(fs.readdirSync(uploads).includes('x'), true);
5657
deepEqual(fs.readdirSync(`${uploads}/x`), ['y']);
5758
deepEqual(fs.readdirSync(`${uploads}/x/y`), ['z.txt']);
5859
strictEqual(res.status, 200);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {Readable} from 'node:stream';
2+
import MultipartParser from '../../src/parsers/Multipart.js';
3+
import {malformedMultipart} from '../../src/FormidableError.js';
4+
5+
import test from 'node:test';
6+
import assert, { deepEqual } from 'node:assert';
7+
8+
9+
10+
test('MultipartParser does not hang', async (t) => {
11+
const mime = `--_\r\n--_--\r\n`;
12+
const parser = new MultipartParser();
13+
parser.initWithBoundary('_');
14+
try {
15+
for await (const {name, buffer, start, end} of Readable.from(mime).pipe(parser)) {
16+
console.log(name, buffer ? buffer.subarray(start, end).toString() : '');
17+
}
18+
19+
} catch (e) {
20+
deepEqual(e.code, malformedMultipart)
21+
return;
22+
// console.error('error');
23+
// console.error(e);
24+
25+
}
26+
assert(false, 'should catch error');
27+
});

0 commit comments

Comments
 (0)