Skip to content

Commit 1d0aba7

Browse files
feat: add min size and empty file validation options (#662)
* feat: add min size and empty file validation options
1 parent a1f62b5 commit 1d0aba7

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ See it's defaults in [src/Formidable.js](./src/Formidable.js#L14-L22) (the
319319
placing file uploads in. You can move them later by using `fs.rename()`
320320
- `options.keepExtensions` **{boolean}** - default `false`; to include the
321321
extensions of the original files or not
322+
- `options.allowEmptyFiles` **{boolean}** - default `true`; allow upload empty
323+
files
324+
- `options.minFileSize` **{number}** - default `1` (1byte); the minium size of
325+
uploaded file.
322326
- `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb);
323327
limit the size of uploaded file.
324328
- `options.maxFields` **{number}** - default `1000`; limit the number of fields

src/Formidable.js

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const DEFAULT_OPTIONS = {
1818
maxFields: 1000,
1919
maxFieldsSize: 20 * 1024 * 1024,
2020
maxFileSize: 200 * 1024 * 1024,
21+
minFileSize: 1,
22+
allowEmptyFiles: true,
2123
keepExtensions: false,
2224
encoding: 'utf-8',
2325
hash: false,
@@ -309,6 +311,14 @@ class IncomingForm extends EventEmitter {
309311

310312
part.on('data', (buffer) => {
311313
this._fileSize += buffer.length;
314+
if (this._fileSize < this.options.minFileSize) {
315+
this._error(
316+
new Error(
317+
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${this._fileSize} bytes of file data`,
318+
),
319+
);
320+
return;
321+
}
312322
if (this._fileSize > this.options.maxFileSize) {
313323
this._error(
314324
new Error(
@@ -327,6 +337,15 @@ class IncomingForm extends EventEmitter {
327337
});
328338

329339
part.on('end', () => {
340+
if (!this.options.allowEmptyFiles && this._fileSize === 0) {
341+
this._error(
342+
new Error(
343+
`options.allowEmptyFiles is false, file size should be greather than 0`,
344+
),
345+
);
346+
return;
347+
}
348+
330349
file.end(() => {
331350
this._flushing -= 1;
332351
this.emit('file', part.name, file);

test/unit/formidable.test.js

+85
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'use strict';
55

66
const path = require('path');
7+
const Stream = require('stream');
78
// const assert = require('assert');
89
const Request = require('http').ClientRequest;
910

@@ -159,6 +160,90 @@ function makeHeader(filename) {
159160
form.emit('end');
160161
});
161162

163+
describe(`${name}#_onPart`, () => {
164+
describe('when not allow empty files', () => {
165+
describe('when file is empty', () => {
166+
test('emits error when part is received', (done) => {
167+
const form = getForm(name, {
168+
multiples: true,
169+
allowEmptyFiles: false,
170+
});
171+
172+
const part = new Stream();
173+
part.mime = 'text/plain';
174+
// eslint-disable-next-line max-nested-callbacks
175+
form.on('error', (error) => {
176+
expect(error.message).toBe(
177+
'options.allowEmptyFiles is false, file size should be greather than 0',
178+
);
179+
done();
180+
});
181+
form.onPart(part);
182+
part.emit('end');
183+
});
184+
});
185+
186+
describe('when file is not empty', () => {
187+
test('not emits error when part is received', () => {
188+
const form = getForm(name, {
189+
multiples: true,
190+
allowEmptyFiles: false,
191+
});
192+
const formEmitSpy = jest.spyOn(form, 'emit');
193+
194+
const part = new Stream();
195+
part.mime = 'text/plain';
196+
form.onPart(part);
197+
part.emit('data', Buffer.alloc(1));
198+
expect(formEmitSpy).not.toBeCalledWith('error');
199+
});
200+
});
201+
});
202+
203+
describe('when allow empty files', () => {
204+
test('not emits error when part is received', () => {
205+
const form = getForm(name, { multiples: true });
206+
const formEmitSpy = jest.spyOn(form, 'emit');
207+
208+
const part = new Stream();
209+
part.mime = 'text/plain';
210+
form.onPart(part);
211+
part.emit('end');
212+
expect(formEmitSpy).not.toBeCalledWith('error');
213+
});
214+
});
215+
216+
describe('when file uploaded size is inferior than minFileSize option', () => {
217+
test('emits error when part is received', (done) => {
218+
const form = getForm(name, { multiples: true, minFileSize: 5 });
219+
220+
const part = new Stream();
221+
part.mime = 'text/plain';
222+
form.on('error', (error) => {
223+
expect(error.message).toBe(
224+
'options.minFileSize (5 bytes) inferior, received 4 bytes of file data',
225+
);
226+
done();
227+
});
228+
form.onPart(part);
229+
part.emit('data', Buffer.alloc(4));
230+
});
231+
});
232+
233+
describe('when file uploaded size is superior than minFileSize option', () => {
234+
test('not emits error when part is received', () => {
235+
const form = getForm(name, { multiples: true, minFileSize: 10 });
236+
const formEmitSpy = jest.spyOn(form, 'emit');
237+
238+
const part = new Stream();
239+
part.mime = 'text/plain';
240+
form.onPart(part);
241+
part.emit('data', Buffer.alloc(11));
242+
expect(formEmitSpy).not.toBeCalledWith('error');
243+
});
244+
});
245+
});
246+
162247
// test(`${name}: use custom options.filename instead of form._uploadPath`, () => {
163248
// const form = getForm(name, {
164249
// filename: (_) => path.join(__dirname, 'sasa'),

0 commit comments

Comments
 (0)