🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

multer

Package Overview
Dependencies
Maintainers
5
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

multer - npm Package Compare versions

Comparing version
2.0.0
to
3.0.0-alpha.1
+21
lib/error.js
const errorMessages = new Map([
['CLIENT_ABORTED', 'Client aborted'],
['LIMIT_FILE_SIZE', 'File too large'],
['LIMIT_FILE_COUNT', 'Too many files'],
['LIMIT_FIELD_KEY', 'Field name too long'],
['LIMIT_FIELD_VALUE', 'Field value too long'],
['LIMIT_FIELD_COUNT', 'Too many fields'],
['LIMIT_UNEXPECTED_FILE', 'Unexpected file field']
])
export default class MulterError extends Error {
constructor (code, optionalField) {
super(errorMessages.get(code))
this.code = code
this.name = this.constructor.name
if (optionalField) this.field = optionalField
Error.captureStackTrace(this, this.constructor)
}
}
import MulterError from './error.js'
export default function createFileFilter (fields) {
const filesLeft = new Map()
for (const field of fields) {
if (typeof field.maxCount === 'number') {
filesLeft.set(field.name, field.maxCount)
} else {
filesLeft.set(field.name, Infinity)
}
}
return function fileFilter (file) {
if (!filesLeft.has(file.fieldName)) {
throw new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldName)
}
const left = filesLeft.get(file.fieldName)
if (left <= 0) {
throw new MulterError('LIMIT_FILE_COUNT', file.fieldName)
}
filesLeft.set(file.fieldName, left - 1)
}
}
import fs from 'node:fs'
import appendField from 'append-field'
import is from 'type-is'
import createFileAppender from './file-appender.js'
import readBody from './read-body.js'
async function handleRequest (setup, req) {
const options = setup()
const result = await readBody(req, options.limits, options.fileFilter)
req.body = Object.create(null)
for (const field of result.fields) {
appendField(req.body, field.key, field.value)
}
const appendFile = createFileAppender(options.fileStrategy, req, options.fields)
for (const file of result.files) {
file.stream = fs.createReadStream(file.path)
file.stream.on('open', () => fs.unlink(file.path, () => {}))
appendFile(file)
}
}
export default function createMiddleware (setup) {
return function multerMiddleware (req, _, next) {
if (!is(req, ['multipart'])) return next()
handleRequest(setup, req).then(next, next)
}
}
import { extname } from 'node:path'
import { pipeline as _pipeline } from 'node:stream'
import { promisify } from 'node:util'
import Busboy from '@fastify/busboy'
import { createWriteStream } from 'fs-temp'
import hasOwnProperty from 'has-own-property'
import _onFinished from 'on-finished'
import FileType from 'stream-file-type'
import MulterError from './error.js'
const onFinished = promisify(_onFinished)
const pipeline = promisify(_pipeline)
function drainStream (stream) {
stream.on('readable', stream.read.bind(stream))
}
function collectFields (busboy, limits) {
return new Promise((resolve, reject) => {
const result = []
busboy.on('field', (fieldname, value, fieldnameTruncated, valueTruncated) => {
// Currently not implemented (https://github.com/mscdex/busboy/issues/6)
/* c8 ignore next */
if (fieldnameTruncated) return reject(new MulterError('LIMIT_FIELD_KEY'))
if (valueTruncated) return reject(new MulterError('LIMIT_FIELD_VALUE', fieldname))
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) {
return reject(new MulterError('LIMIT_FIELD_KEY'))
}
result.push({ key: fieldname, value: value })
})
busboy.on('finish', () => resolve(result))
})
}
function collectFiles (busboy, limits, fileFilter) {
return new Promise((resolve, reject) => {
const result = []
busboy.on('file', async (fieldname, fileStream, filename, encoding, mimetype) => {
// Catch all errors on file stream
fileStream.on('error', reject)
// Catch limit exceeded on file stream
fileStream.on('limit', () => {
reject(new MulterError('LIMIT_FILE_SIZE', fieldname))
})
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) {
return reject(new MulterError('LIMIT_FIELD_KEY'))
}
const file = {
fieldName: fieldname,
originalName: filename,
clientReportedMimeType: mimetype,
clientReportedFileExtension: extname(filename || '')
}
try {
fileFilter(file)
} catch (err) {
return reject(err)
}
const target = createWriteStream()
const detector = new FileType()
const fileClosed = new Promise((resolve) => target.on('close', resolve))
const promise = pipeline(fileStream, detector, target)
.then(async () => {
await fileClosed
file.path = target.path
file.size = target.bytesWritten
const fileType = await detector.fileTypePromise()
file.detectedMimeType = (fileType ? fileType.mime : null)
file.detectedFileExtension = (fileType ? `.${fileType.ext}` : '')
return file
})
.catch(reject)
result.push(promise)
})
busboy.on('finish', () => resolve(Promise.all(result)))
})
}
export default async function readBody (req, limits, fileFilter) {
const busboy = new Busboy({ headers: req.headers, limits: limits })
const fields = collectFields(busboy, limits)
const files = collectFiles(busboy, limits, fileFilter)
const guard = new Promise((resolve, reject) => {
req.on('error', (err) => reject(err))
busboy.on('error', (err) => reject(err))
req.on('aborted', () => reject(new MulterError('CLIENT_ABORTED')))
busboy.on('filesLimit', () => reject(new MulterError('LIMIT_FILE_COUNT')))
busboy.on('fieldsLimit', () => reject(new MulterError('LIMIT_FIELD_COUNT')))
busboy.on('finish', resolve)
})
req.pipe(busboy)
try {
const result = await Promise.all([fields, files, guard])
return { fields: result[0], files: result[1] }
} catch (err) {
req.unpipe(busboy)
drainStream(req)
busboy.removeAllListeners()
// Wait for request to close, finish, or error
await onFinished(req).catch(/* c8 ignore next: Already handled by req.on('error', _) */ () => {})
throw err
}
}
+51
-83

@@ -1,104 +0,72 @@

var makeMiddleware = require('./lib/make-middleware')
import bytes from 'bytes'
var diskStorage = require('./storage/disk')
var memoryStorage = require('./storage/memory')
var MulterError = require('./lib/multer-error')
import createFileFilter from './lib/file-filter.js'
import createMiddleware from './lib/middleware.js'
function allowAll (req, file, cb) {
cb(null, true)
const kLimits = Symbol('limits')
function parseLimit (limits, key, defaultValue) {
const input = limits[key] == null ? defaultValue : limits[key]
const value = bytes.parse(input)
if (!Number.isFinite(value)) throw new Error(`Invalid limit "${key}" given: ${limits[key]}`)
if (!Number.isInteger(value)) throw new Error(`Invalid limit "${key}" given: ${value}`)
return value
}
function Multer (options) {
if (options.storage) {
this.storage = options.storage
} else if (options.dest) {
this.storage = diskStorage({ destination: options.dest })
} else {
this.storage = memoryStorage()
}
this.limits = options.limits
this.preservePath = options.preservePath
this.fileFilter = options.fileFilter || allowAll
function _middleware (limits, fields, fileStrategy) {
return createMiddleware(() => ({
fields: fields,
limits: limits,
fileFilter: createFileFilter(fields),
fileStrategy: fileStrategy
}))
}
Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
function setup () {
var fileFilter = this.fileFilter
var filesLeft = Object.create(null)
fields.forEach(function (field) {
if (typeof field.maxCount === 'number') {
filesLeft[field.name] = field.maxCount
} else {
filesLeft[field.name] = Infinity
}
})
function wrappedFileFilter (req, file, cb) {
if ((filesLeft[file.fieldname] || 0) <= 0) {
return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname))
}
filesLeft[file.fieldname] -= 1
fileFilter(req, file, cb)
class Multer {
constructor (options) {
this[kLimits] = {
fieldNameSize: parseLimit(options.limits || {}, 'fieldNameSize', '100B'),
fieldSize: parseLimit(options.limits || {}, 'fieldSize', '8KB'),
fields: parseLimit(options.limits || {}, 'fields', 1000),
fileSize: parseLimit(options.limits || {}, 'fileSize', '8MB'),
files: parseLimit(options.limits || {}, 'files', 10),
headerPairs: parseLimit(options.limits || {}, 'headerPairs', 2000)
}
}
return {
limits: this.limits,
preservePath: this.preservePath,
storage: this.storage,
fileFilter: wrappedFileFilter,
fileStrategy: fileStrategy
}
single (name) {
return _middleware(this[kLimits], [{ name: name, maxCount: 1 }], 'VALUE')
}
return makeMiddleware(setup.bind(this))
}
array (name, maxCount) {
return _middleware(this[kLimits], [{ name: name, maxCount: maxCount }], 'ARRAY')
}
Multer.prototype.single = function (name) {
return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE')
}
fields (fields) {
return _middleware(this[kLimits], fields, 'OBJECT')
}
Multer.prototype.array = function (name, maxCount) {
return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY')
}
none () {
return _middleware(this[kLimits], [], 'NONE')
}
Multer.prototype.fields = function (fields) {
return this._makeMiddleware(fields, 'OBJECT')
}
Multer.prototype.none = function () {
return this._makeMiddleware([], 'NONE')
}
Multer.prototype.any = function () {
function setup () {
return {
limits: this.limits,
preservePath: this.preservePath,
storage: this.storage,
fileFilter: this.fileFilter,
any () {
return createMiddleware(() => ({
fields: [],
limits: this[kLimits],
fileFilter: () => {},
fileStrategy: 'ARRAY'
}
}))
}
return makeMiddleware(setup.bind(this))
}
function multer (options) {
if (options === undefined) {
return new Multer({})
}
export default function multer (options = {}) {
if (options === null) throw new TypeError('Expected object for argument "options", got null')
if (typeof options !== 'object') throw new TypeError(`Expected object for argument "options", got ${typeof options}`)
if (typeof options === 'object' && options !== null) {
return new Multer(options)
if (options.dest || options.storage || options.fileFilter) {
throw new Error('The "dest", "storage" and "fileFilter" options where removed in Multer 2.0. Please refer to the latest documentation for new usage.')
}
throw new TypeError('Expected object for argument options')
return new Multer(options)
}
module.exports = multer
module.exports.diskStorage = diskStorage
module.exports.memoryStorage = memoryStorage
module.exports.MulterError = MulterError

@@ -1,67 +0,24 @@

var objectAssign = require('object-assign')
function arrayRemove (arr, item) {
var idx = arr.indexOf(item)
if (~idx) arr.splice(idx, 1)
}
function FileAppender (strategy, req) {
this.strategy = strategy
this.req = req
export default function createFileAppender (strategy, req, fields) {
switch (strategy) {
case 'NONE': break
case 'VALUE': break
case 'VALUE': req.file = null; break
case 'ARRAY': req.files = []; break
case 'OBJECT': req.files = Object.create(null); break
default: throw new Error('Unknown file strategy: ' + strategy)
/* c8 ignore next */
default: throw new Error(`Unknown file strategy: ${strategy}`)
}
}
FileAppender.prototype.insertPlaceholder = function (file) {
var placeholder = {
fieldname: file.fieldname
if (strategy === 'OBJECT') {
for (const field of fields) {
req.files[field.name] = []
}
}
switch (this.strategy) {
case 'NONE': break
case 'VALUE': break
case 'ARRAY': this.req.files.push(placeholder); break
case 'OBJECT':
if (this.req.files[file.fieldname]) {
this.req.files[file.fieldname].push(placeholder)
} else {
this.req.files[file.fieldname] = [placeholder]
}
break
return function append (file) {
switch (strategy) {
case 'VALUE': req.file = file; break
case 'ARRAY': req.files.push(file); break
case 'OBJECT': req.files[file.fieldName].push(file); break
}
}
return placeholder
}
FileAppender.prototype.removePlaceholder = function (placeholder) {
switch (this.strategy) {
case 'NONE': break
case 'VALUE': break
case 'ARRAY': arrayRemove(this.req.files, placeholder); break
case 'OBJECT':
if (this.req.files[placeholder.fieldname].length === 1) {
delete this.req.files[placeholder.fieldname]
} else {
arrayRemove(this.req.files[placeholder.fieldname], placeholder)
}
break
}
}
FileAppender.prototype.replacePlaceholder = function (placeholder, file) {
if (this.strategy === 'VALUE') {
this.req.file = file
return
}
delete placeholder.fieldname
objectAssign(placeholder, file)
}
module.exports = FileAppender
{
"name": "multer",
"description": "Middleware for handling `multipart/form-data`.",
"version": "2.0.0",
"version": "3.0.0-alpha.1",
"contributors": [

@@ -12,2 +12,4 @@ "Hage Yaapa <captain@hacksparrow.com> (http://www.hacksparrow.com)",

"repository": "expressjs/multer",
"type": "module",
"exports": "./index.js",
"keywords": [

@@ -23,22 +25,24 @@ "form",

"dependencies": {
"append-field": "^1.0.0",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
"@fastify/busboy": "^1.0.0",
"append-field": "^2.0.0",
"bytes": "^3.1.0",
"fs-temp": "^2.0.1",
"has-own-property": "^2.0.0",
"on-finished": "^2.3.0",
"stream-file-type": "^0.6.1",
"type-is": "^1.6.18"
},
"devDependencies": {
"deep-equal": "^2.0.3",
"express": "^4.13.1",
"form-data": "^1.0.0-rc1",
"fs-temp": "^1.1.2",
"mocha": "^11.3.0",
"rimraf": "^2.4.1",
"standard": "^14.3.3",
"c8": "^7.7.3",
"express": "^4.16.4",
"form-data": "^4.0.0",
"get-stream": "^6.0.1",
"hasha": "^5.2.0",
"mocha": "^9.0.3",
"recursive-nullify": "^1.0.0",
"standard": "^16.0.3",
"testdata-w3c-json-form": "^1.0.0"
},
"engines": {
"node": ">= 10.16.0"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},

@@ -48,12 +52,7 @@ "files": [

"index.js",
"storage/",
"lib/"
],
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"test": "mocha --reporter spec --exit --check-leaks test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
"test": "standard && c8 --check-coverage --statements 100 mocha"
}
}
+48
-209

@@ -1,2 +0,2 @@

# Multer [![Build Status](https://badgen.net/github/checks/expressjs/multer/master?label=ci)](https://github.com/expressjs/multer/actions/workflows/ci.yml) [![Test Coverage](https://badgen.net/coveralls/c/github/expressjs/multer/master)](https://coveralls.io/r/expressjs/multer?branch=master) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
# Multer [![Build Status](https://travis-ci.org/expressjs/multer.svg?branch=master)](https://travis-ci.org/expressjs/multer) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)

@@ -8,17 +8,6 @@ Multer is a node.js middleware for handling `multipart/form-data`, which is primarily used for uploading files. It is written

## Translations
This README is also available in other languages:
- [Español](https://github.com/expressjs/multer/blob/master/doc/README-es.md) (Spanish)
- [简体中文](https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md) (Chinese)
- [한국어](https://github.com/expressjs/multer/blob/master/doc/README-ko.md) (Korean)
- [Русский язык](https://github.com/expressjs/multer/blob/master/doc/README-ru.md) (Russian)
- [Việt Nam](https://github.com/expressjs/multer/blob/master/doc/README-vi.md) (Vietnam)
- [Português](https://github.com/expressjs/multer/blob/master/doc/README-pt-br.md) (Portuguese Brazil)
## Installation
```sh
$ npm install --save multer
npm install --save multer
```

@@ -32,18 +21,10 @@

Don't forget the `enctype="multipart/form-data"` in your form.
```html
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>
```
```javascript
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
import multer from 'multer'
import express from 'express'
const app = express()
const upload = multer()
app.post('/profile', upload.single('avatar'), function (req, res, next) {
app.post('/profile', upload.single('avatar'), (req, res, next) => {
// req.file is the `avatar` file

@@ -53,3 +34,3 @@ // req.body will hold the text fields, if there were any

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
app.post('/photos/upload', upload.array('photos', 12), (req, res, next) => {
// req.files is array of `photos` files

@@ -60,3 +41,3 @@ // req.body will contain the text fields, if there were any

const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
app.post('/cool-profile', cpUpload, (req, res, next) => {
// req.files is an object (String -> Array) where fieldname is the key, and the value is array of files

@@ -72,11 +53,12 @@ //

In case you need to handle a text-only multipart form, you should use the `.none()` method:
In case you need to handle a text-only multipart form, you can use the `.none()` method, example:
```javascript
const express = require('express')
import multer from 'multer'
import express from 'express'
const app = express()
const multer = require('multer')
const upload = multer()
app.post('/profile', upload.none(), function (req, res, next) {
app.post('/profile', upload.none(), (req, res, next) => {
// req.body contains the text fields

@@ -86,28 +68,2 @@ })

Here's an example on how multer is used an HTML form. Take special note of the `enctype="multipart/form-data"` and `name="uploaded_file"` fields:
```html
<form action="/stats" enctype="multipart/form-data" method="post">
<div class="form-group">
<input type="file" class="form-control-file" name="uploaded_file">
<input type="text" class="form-control" placeholder="Number of speakers" name="nspeakers">
<input type="submit" value="Get me the stats!" class="btn btn-default">
</div>
</form>
```
Then in your javascript file you would add these lines to access both the file and the body. It is important that you use the `name` field value from the form in your upload function. This tells multer which field on the request it should look for the files in. If these fields aren't the same in the HTML form and on your server, your upload will fail:
```javascript
const multer = require('multer')
const upload = multer({ dest: './public/data/uploads/' })
app.post('/stats', upload.single('uploaded_file'), function (req, res) {
// req.file is the name of your file in the form above, here 'uploaded_file'
// req.body will hold the text fields, if there were any
console.log(req.file, req.body)
});
```
## API

@@ -119,43 +75,24 @@

Key | Description | Note
--- | --- | ---
`fieldname` | Field name specified in the form |
`originalname` | Name of the file on the user's computer |
`encoding` | Encoding type of the file |
`mimetype` | Mime type of the file |
`size` | Size of the file in bytes |
`destination` | The folder to which the file has been saved | `DiskStorage`
`filename` | The name of the file within the `destination` | `DiskStorage`
`path` | The full path to the uploaded file | `DiskStorage`
`buffer` | A `Buffer` of the entire file | `MemoryStorage`
### `multer(opts)`
Multer accepts an options object, the most basic of which is the `dest`
property, which tells Multer where to upload the files. In case you omit the
options object, the files will be kept in memory and never written to disk.
By default, Multer will rename the files so as to avoid naming conflicts. The
renaming function can be customized according to your needs.
The following are the options that can be passed to Multer.
Key | Description
--- | ---
`dest` or `storage` | Where to store the files
`fileFilter` | Function to control which files are accepted
`limits` | Limits of the uploaded data
`preservePath` | Keep the full path of files instead of just the base name
`fieldName` | Field name specified in the form
`originalName` | Name of the file on the user's computer (`undefined` if no filename was supplied by the client)
`size` | Total size of the file in bytes
`stream` | Readable stream of file data
`detectedMimeType` | The detected mime-type, or null if we failed to detect
`detectedFileExtension` | The typical file extension for files of the detected type, or empty string if we failed to detect (with leading `.` to match `path.extname`)
`clientReportedMimeType` | The mime type reported by the client using the `Content-Type` header, or null<sup>1</sup> if the header was absent
`clientReportedFileExtension` | The extension of the file uploaded (as reported by `path.extname`)
In an average web app, only `dest` might be required, and configured as shown in
the following example.
<sup>1</sup> Currently returns `text/plain` if header is absent, this is a bug and it will be fixed in a patch release. Do not rely on this behavior.
```javascript
const upload = multer({ dest: 'uploads/' })
```
### `multer(opts)`
If you want more control over your uploads, you'll want to use the `storage`
option instead of `dest`. Multer ships with storage engines `DiskStorage`
and `MemoryStorage`; More engines are available from third parties.
Multer accepts an options object, the following are the options that can be
passed to Multer.
Key | Description
-------- | -----------
`limits` | Limits of the uploaded data [(full description)](#limits)
#### `.single(fieldname)`

@@ -190,3 +127,3 @@

Accept only text fields. If any file upload is made, error with code
"LIMIT\_UNEXPECTED\_FILE" will be issued.
"LIMIT\_UNEXPECTED\_FILE" will be issued. This is the same as doing `upload.fields([])`.

@@ -203,68 +140,2 @@ #### `.any()`

### `storage`
#### `DiskStorage`
The disk storage engine gives you full control on storing files to disk.
```javascript
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({ storage: storage })
```
There are two options available, `destination` and `filename`. They are both
functions that determine where the file should be stored.
`destination` is used to determine within which folder the uploaded files should
be stored. This can also be given as a `string` (e.g. `'/tmp/uploads'`). If no
`destination` is given, the operating system's default directory for temporary
files is used.
**Note:** You are responsible for creating the directory when providing
`destination` as a function. When passing a string, multer will make sure that
the directory is created for you.
`filename` is used to determine what the file should be named inside the folder.
If no `filename` is given, each file will be given a random name that doesn't
include any file extension.
**Note:** Multer will not append any file extension for you, your function
should return a filename complete with an file extension.
Each function gets passed both the request (`req`) and some information about
the file (`file`) to aid with the decision.
Note that `req.body` might not have been fully populated yet. It depends on the
order that the client transmits fields and files to the server.
For understanding the calling convention used in the callback (needing to pass
null as the first param), refer to
[Node.js error handling](https://www.joyent.com/node-js/production/design/errors)
#### `MemoryStorage`
The memory storage engine stores the files in memory as `Buffer` objects. It
doesn't have any options.
```javascript
const storage = multer.memoryStorage()
const upload = multer({ storage: storage })
```
When using memory storage, the file info will contain a field called
`buffer` that contains the entire file.
**WARNING**: Uploading very large files, or relatively small files in large
numbers very quickly, can cause your application to run out of memory when
memory storage is used.
### `limits`

@@ -274,70 +145,38 @@

The following integer values are available:
The following limits are available:
Key | Description | Default
--- | --- | ---
`fieldNameSize` | Max field name size | 100 bytes
`fieldSize` | Max field value size (in bytes) | 1MB
`fields` | Max number of non-file fields | Infinity
`fileSize` | For multipart forms, the max file size (in bytes) | Infinity
`files` | For multipart forms, the max number of file fields | Infinity
`parts` | For multipart forms, the max number of parts (fields + files) | Infinity
`headerPairs` | For multipart forms, the max number of header key=>value pairs to parse | 2000
`fieldNameSize` | Max number of bytes per field name | `'100B'`
`fieldSize` | Max number of bytes per field value | `'8KB'`
`fields` | Max number of fields per request | `1000`
`fileSize` | Max number of bytes per file | `'8MB'`
`files` | Max number of files per request | `10`
`headerPairs` | Max number of header key-value pairs | `2000` (same as Node's http)
Bytes limits can be passed either as a number, or as a string with an appropriate prefix.
Specifying the limits can help protect your site against denial of service (DoS) attacks.
### `fileFilter`
Set this to a function to control which files should be uploaded and which
should be skipped. The function should look like this:
```javascript
function fileFilter (req, file, cb) {
// The function should call `cb` with a boolean
// to indicate if the file should be accepted
// To reject this file pass `false`, like so:
cb(null, false)
// To accept the file pass `true`, like so:
cb(null, true)
// You can always pass an error if something goes wrong:
cb(new Error('I don\'t have a clue!'))
}
```
## Error handling
When encountering an error, Multer will delegate the error to Express. You can
When encountering an error, multer will delegate the error to express. You can
display a nice error page using [the standard express way](http://expressjs.com/guide/error-handling.html).
If you want to catch errors specifically from Multer, you can call the
middleware function by yourself. Also, if you want to catch only [the Multer errors](https://github.com/expressjs/multer/blob/master/lib/multer-error.js), you can use the `MulterError` class that is attached to the `multer` object itself (e.g. `err instanceof multer.MulterError`).
If you want to catch errors specifically from multer, you can call the
middleware function by yourself.
```javascript
const multer = require('multer')
const upload = multer().single('avatar')
app.post('/profile', function (req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
} else if (err) {
// An unknown error occurred when uploading.
app.post('/profile', (req, res) => {
upload(req, res, (err) => {
if (err) {
// An error occurred when uploading
return
}
// Everything went fine.
// Everything went fine
})
})
```
## Custom storage engine
For information on how to build your own storage engine, see [Multer Storage Engine](https://github.com/expressjs/multer/blob/master/StorageEngine.md).
## License
[MIT](LICENSE)
var EventEmitter = require('events').EventEmitter
function Counter () {
EventEmitter.call(this)
this.value = 0
}
Counter.prototype = Object.create(EventEmitter.prototype)
Counter.prototype.increment = function increment () {
this.value++
}
Counter.prototype.decrement = function decrement () {
if (--this.value === 0) this.emit('zero')
}
Counter.prototype.isZero = function isZero () {
return (this.value === 0)
}
Counter.prototype.onceZero = function onceZero (fn) {
if (this.isZero()) return fn()
this.once('zero', fn)
}
module.exports = Counter
var is = require('type-is')
var Busboy = require('busboy')
var extend = require('xtend')
var appendField = require('append-field')
var Counter = require('./counter')
var MulterError = require('./multer-error')
var FileAppender = require('./file-appender')
var removeUploadedFiles = require('./remove-uploaded-files')
function drainStream (stream) {
stream.on('readable', () => {
while (stream.read() !== null) {}
})
}
function makeMiddleware (setup) {
return function multerMiddleware (req, res, next) {
if (!is(req, ['multipart'])) return next()
var options = setup()
var limits = options.limits
var storage = options.storage
var fileFilter = options.fileFilter
var fileStrategy = options.fileStrategy
var preservePath = options.preservePath
req.body = Object.create(null)
req.on('error', function (err) {
abortWithError(err)
})
var busboy
try {
busboy = Busboy({ headers: req.headers, limits: limits, preservePath: preservePath })
} catch (err) {
return next(err)
}
var appender = new FileAppender(fileStrategy, req)
var isDone = false
var readFinished = false
var errorOccured = false
var pendingWrites = new Counter()
var uploadedFiles = []
function done (err) {
if (isDone) return
isDone = true
req.unpipe(busboy)
drainStream(req)
req.resume()
setImmediate(() => {
busboy.removeAllListeners()
})
next(err)
}
function indicateDone () {
if (readFinished && pendingWrites.isZero() && !errorOccured) done()
}
function abortWithError (uploadError) {
if (errorOccured) return
errorOccured = true
pendingWrites.onceZero(function () {
function remove (file, cb) {
storage._removeFile(req, file, cb)
}
removeUploadedFiles(uploadedFiles, remove, function (err, storageErrors) {
if (err) return done(err)
uploadError.storageErrors = storageErrors
done(uploadError)
})
})
}
function abortWithCode (code, optionalField) {
abortWithError(new MulterError(code, optionalField))
}
// handle text field data
busboy.on('field', function (fieldname, value, { nameTruncated, valueTruncated }) {
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
if (nameTruncated) return abortWithCode('LIMIT_FIELD_KEY')
if (valueTruncated) return abortWithCode('LIMIT_FIELD_VALUE', fieldname)
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY')
}
appendField(req.body, fieldname, value)
})
// handle files
busboy.on('file', function (fieldname, fileStream, { filename, encoding, mimeType }) {
// don't attach to the files object, if there is no file
if (!filename) return fileStream.resume()
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY')
}
var file = {
fieldname: fieldname,
originalname: filename,
encoding: encoding,
mimetype: mimeType
}
var placeholder = appender.insertPlaceholder(file)
fileFilter(req, file, function (err, includeFile) {
if (err) {
appender.removePlaceholder(placeholder)
return abortWithError(err)
}
if (!includeFile) {
appender.removePlaceholder(placeholder)
return fileStream.resume()
}
var aborting = false
pendingWrites.increment()
Object.defineProperty(file, 'stream', {
configurable: true,
enumerable: false,
value: fileStream
})
fileStream.on('error', function (err) {
pendingWrites.decrement()
abortWithError(err)
})
fileStream.on('limit', function () {
aborting = true
abortWithCode('LIMIT_FILE_SIZE', fieldname)
})
storage._handleFile(req, file, function (err, info) {
if (aborting) {
appender.removePlaceholder(placeholder)
uploadedFiles.push(extend(file, info))
return pendingWrites.decrement()
}
if (err) {
appender.removePlaceholder(placeholder)
pendingWrites.decrement()
return abortWithError(err)
}
var fileInfo = extend(file, info)
appender.replacePlaceholder(placeholder, fileInfo)
uploadedFiles.push(fileInfo)
pendingWrites.decrement()
indicateDone()
})
})
})
busboy.on('error', function (err) { abortWithError(err) })
busboy.on('partsLimit', function () { abortWithCode('LIMIT_PART_COUNT') })
busboy.on('filesLimit', function () { abortWithCode('LIMIT_FILE_COUNT') })
busboy.on('fieldsLimit', function () { abortWithCode('LIMIT_FIELD_COUNT') })
busboy.on('close', function () {
readFinished = true
indicateDone()
})
req.pipe(busboy)
}
}
module.exports = makeMiddleware
var util = require('util')
var errorMessages = {
LIMIT_PART_COUNT: 'Too many parts',
LIMIT_FILE_SIZE: 'File too large',
LIMIT_FILE_COUNT: 'Too many files',
LIMIT_FIELD_KEY: 'Field name too long',
LIMIT_FIELD_VALUE: 'Field value too long',
LIMIT_FIELD_COUNT: 'Too many fields',
LIMIT_UNEXPECTED_FILE: 'Unexpected field',
MISSING_FIELD_NAME: 'Field name missing'
}
function MulterError (code, field) {
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = errorMessages[code]
this.code = code
if (field) this.field = field
}
util.inherits(MulterError, Error)
module.exports = MulterError
function removeUploadedFiles (uploadedFiles, remove, cb) {
var length = uploadedFiles.length
var errors = []
if (length === 0) return cb(null, errors)
function handleFile (idx) {
var file = uploadedFiles[idx]
remove(file, function (err) {
if (err) {
err.file = file
err.field = file.fieldname
errors.push(err)
}
if (idx < length - 1) {
handleFile(idx + 1)
} else {
cb(null, errors)
}
})
}
handleFile(0)
}
module.exports = removeUploadedFiles
var fs = require('fs')
var os = require('os')
var path = require('path')
var crypto = require('crypto')
var mkdirp = require('mkdirp')
function getFilename (req, file, cb) {
crypto.randomBytes(16, function (err, raw) {
cb(err, err ? undefined : raw.toString('hex'))
})
}
function getDestination (req, file, cb) {
cb(null, os.tmpdir())
}
function DiskStorage (opts) {
this.getFilename = (opts.filename || getFilename)
if (typeof opts.destination === 'string') {
mkdirp.sync(opts.destination)
this.getDestination = function ($0, $1, cb) { cb(null, opts.destination) }
} else {
this.getDestination = (opts.destination || getDestination)
}
}
DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) {
var that = this
that.getDestination(req, file, function (err, destination) {
if (err) return cb(err)
that.getFilename(req, file, function (err, filename) {
if (err) return cb(err)
var finalPath = path.join(destination, filename)
var outStream = fs.createWriteStream(finalPath)
file.stream.pipe(outStream)
outStream.on('error', cb)
outStream.on('finish', function () {
cb(null, {
destination: destination,
filename: filename,
path: finalPath,
size: outStream.bytesWritten
})
})
})
})
}
DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) {
var path = file.path
delete file.destination
delete file.filename
delete file.path
fs.unlink(path, cb)
}
module.exports = function (opts) {
return new DiskStorage(opts)
}
var concat = require('concat-stream')
function MemoryStorage (opts) {}
MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) {
file.stream.pipe(concat({ encoding: 'buffer' }, function (data) {
cb(null, {
buffer: data,
size: data.length
})
}))
}
MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) {
delete file.buffer
cb(null)
}
module.exports = function (opts) {
return new MemoryStorage(opts)
}