+1
-0
@@ -31,2 +31,3 @@ import bytes from 'bytes' | ||
| fields: parseLimit(options.limits || {}, 'fields', 1000), | ||
| fieldNestingDepth: (options.limits || {}).fieldNestingDepth ?? Infinity, | ||
| fileSize: parseLimit(options.limits || {}, 'fileSize', '8MB'), | ||
@@ -33,0 +34,0 @@ files: parseLimit(options.limits || {}, 'files', 10), |
+1
-0
@@ -8,2 +8,3 @@ const errorMessages = new Map([ | ||
| ['LIMIT_FIELD_COUNT', 'Too many fields'], | ||
| ['LIMIT_FIELD_NESTING', 'Field name nesting too deep'], | ||
| ['LIMIT_UNEXPECTED_FILE', 'Unexpected file field'] | ||
@@ -10,0 +11,0 @@ ]) |
+43
-7
@@ -0,1 +1,2 @@ | ||
| import { unlink } from 'node:fs' | ||
| import { extname } from 'node:path' | ||
@@ -7,3 +8,2 @@ import { pipeline as _pipeline } from 'node:stream' | ||
| import { createWriteStream } from 'fs-temp' | ||
| import hasOwnProperty from 'has-own-property' | ||
| import _onFinished from 'on-finished' | ||
@@ -18,3 +18,5 @@ import FileType from 'stream-file-type' | ||
| function drainStream (stream) { | ||
| stream.on('readable', stream.read.bind(stream)) | ||
| stream.on('readable', () => { | ||
| while (stream.read() !== null) { /* drain */ } | ||
| }) | ||
| } | ||
@@ -34,6 +36,12 @@ | ||
| // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) | ||
| if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) { | ||
| if (limits && Object.hasOwn(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) { | ||
| return reject(new MulterError('LIMIT_FIELD_KEY')) | ||
| } | ||
| if (limits && Object.hasOwn(limits, 'fieldNestingDepth')) { | ||
| if (fieldname.split('[').length - 1 > limits.fieldNestingDepth) { | ||
| return reject(new MulterError('LIMIT_FIELD_NESTING', fieldname)) | ||
| } | ||
| } | ||
| result.push({ key: fieldname, value: value }) | ||
@@ -46,3 +54,3 @@ }) | ||
| function collectFiles (busboy, limits, fileFilter) { | ||
| function collectFiles (busboy, limits, fileFilter, pendingTargets, settledPaths) { | ||
| return new Promise((resolve, reject) => { | ||
@@ -61,3 +69,3 @@ const result = [] | ||
| // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) | ||
| if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) { | ||
| if (limits && Object.hasOwn(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) { | ||
| return reject(new MulterError('LIMIT_FIELD_KEY')) | ||
@@ -80,2 +88,3 @@ } | ||
| const target = createWriteStream() | ||
| pendingTargets.add(target) | ||
| const detector = new FileType() | ||
@@ -87,2 +96,6 @@ const fileClosed = new Promise((resolve) => target.on('close', resolve)) | ||
| await fileClosed | ||
| pendingTargets.delete(target) | ||
| // Track the finished file so an abort/error on a *later* file can | ||
| // still remove it; once readBody resolves it is consumed downstream. | ||
| settledPaths.add(target.path) | ||
| file.path = target.path | ||
@@ -97,3 +110,6 @@ file.size = target.bytesWritten | ||
| }) | ||
| .catch(reject) | ||
| .catch((err) => { | ||
| pendingTargets.delete(target) | ||
| reject(err) | ||
| }) | ||
@@ -109,5 +125,7 @@ result.push(promise) | ||
| const busboy = new Busboy({ headers: req.headers, limits: limits }) | ||
| const pendingTargets = new Set() | ||
| const settledPaths = new Set() | ||
| const fields = collectFields(busboy, limits) | ||
| const files = collectFiles(busboy, limits, fileFilter) | ||
| const files = collectFiles(busboy, limits, fileFilter, pendingTargets, settledPaths) | ||
| const guard = new Promise((resolve, reject) => { | ||
@@ -122,2 +140,5 @@ req.on('error', (err) => reject(err)) | ||
| busboy.on('finish', resolve) | ||
| // Fallback when busboy stays silent on a malformed body. | ||
| req.on('close', () => setImmediate(() => reject(new MulterError('CLIENT_ABORTED')))) | ||
| }) | ||
@@ -133,4 +154,19 @@ | ||
| drainStream(req) | ||
| req.resume() | ||
| busboy.removeAllListeners() | ||
| // Clean up any in-flight fs-temp files left behind by aborted/erroring uploads. | ||
| for (const target of pendingTargets) { | ||
| target.destroy() | ||
| if (target.path) unlink(target.path, () => {}) | ||
| } | ||
| pendingTargets.clear() | ||
| // Also remove files that already finished writing: they are no longer in | ||
| // pendingTargets and, since readBody is throwing, will never be consumed. | ||
| for (const settledPath of settledPaths) { | ||
| unlink(settledPath, () => {}) | ||
| } | ||
| settledPaths.clear() | ||
| // Wait for request to close, finish, or error | ||
@@ -137,0 +173,0 @@ await onFinished(req).catch(/* c8 ignore next: Already handled by req.on('error', _) */ () => {}) |
+6
-4
| { | ||
| "name": "multer", | ||
| "description": "Middleware for handling `multipart/form-data`.", | ||
| "version": "3.0.0-alpha.1", | ||
| "version": "3.0.0-alpha.2", | ||
| "contributors": [ | ||
@@ -28,3 +28,2 @@ "Hage Yaapa <captain@hacksparrow.com> (http://www.hacksparrow.com)", | ||
| "fs-temp": "^2.0.1", | ||
| "has-own-property": "^2.0.0", | ||
| "on-finished": "^2.3.0", | ||
@@ -46,3 +45,3 @@ "stream-file-type": "^0.6.1", | ||
| "engines": { | ||
| "node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
| "node": ">= 18" | ||
| }, | ||
@@ -55,4 +54,7 @@ "files": [ | ||
| "scripts": { | ||
| "test": "standard && c8 --check-coverage --statements 100 mocha" | ||
| "lint": "standard", | ||
| "test": "mocha", | ||
| "test:ci": "c8 --reporter=lcovonly --reporter=text --check-coverage --statements 100 npm test", | ||
| "test:cov": "c8 --reporter=html --reporter=text npm test" | ||
| } | ||
| } |
+1
-0
@@ -143,2 +143,3 @@ # Multer [](https://travis-ci.org/expressjs/multer) [](https://badge.fury.io/js/multer) [](https://github.com/feross/standard) | ||
| `fields` | Max number of fields per request | `1000` | ||
| `fieldNestingDepth` | Max number of nesting levels for field names (e.g. `a[b][c]` has 2 levels) | Infinity | ||
| `fileSize` | Max number of bytes per file | `'8MB'` | ||
@@ -145,0 +146,0 @@ `files` | Max number of files per request | `10` |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
19482
9.75%7
-12.5%279
13.88%174
0.58%0
-100%3
50%- Removed
- Removed