Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,75 @@ var diskStorage = require('./storage/disk')
var memoryStorage = require('./storage/memory')
var MulterError = require('./lib/multer-error')

/**
* A file parsed from a multipart request.
*
* @typedef {Object} File
* @property {string} fieldname Name of the form field
* @property {string} originalname Name of the file on the client (client-supplied, treat as untrusted)
* @property {string} encoding Transfer encoding of the file
* @property {string} mimetype MIME type of the file
* @property {number} size Size of the file in bytes
* @property {string} [destination] Folder the file was saved to (`DiskStorage`)
* @property {string} [filename] Name of the file within `destination` (`DiskStorage`)
* @property {string} [path] Full path of the saved file (`DiskStorage`)
* @property {Buffer} [buffer] Contents of the file (`MemoryStorage`)
*/

/**
* Size limits, passed to busboy. All are optional.
*
* @typedef {Object} Limits
* @property {number} [fieldNameSize=100] Max field name size in bytes
* @property {number} [fieldSize=1048576] Max field value size in bytes
* @property {number} [fields=Infinity] Max number of non-file fields
* @property {number} [fileSize=Infinity] Max file size in bytes (integer or `Infinity`)
* @property {number} [files=Infinity] Max number of file fields
* @property {number} [parts=Infinity] Max number of parts (fields + files)
* @property {number} [headerPairs=2000] Max number of header key/value pairs to parse
* @property {number} [fieldNestingDepth=Infinity] Max nesting depth of field names (`a[b][c]` has 2 levels)
* @property {number} [fieldArrayIndexLimit=Infinity] Max numeric array index accepted in field names
*/

/**
* Decides whether a file is accepted. Call `cb(null, true)` to accept the
* file, `cb(null, false)` to skip it silently, or `cb(err)` to abort.
*
* @callback FileFilter
* @param {Object} req The request
* @param {File} file The file being uploaded (without `size`, `path` or `buffer`)
* @param {function(?Error, boolean=): void} cb
*/

/**
* Storage engine. See StorageEngine.md for the full contract.
*
* @typedef {Object} StorageEngine
* @property {function(Object, File, function(?Error, Object=): void): void} _handleFile
* Consumes `file.stream` and calls back with the properties to merge into the file object
* @property {function(Object, File, function(?Error): void): void} _removeFile
* Removes a stored file when the request fails
*/

/**
* @typedef {Object} Options
* @property {string} [dest] Folder to store files in (uses `DiskStorage`)
* @property {StorageEngine} [storage] Storage engine; defaults to `MemoryStorage` when neither `dest` nor `storage` is set
* @property {FileFilter} [fileFilter] Controls which files are accepted
* @property {Limits} [limits] Size limits
* @property {boolean} [preservePath=false] Keep the full client-supplied path in `file.originalname`
* @property {string} [defParamCharset='latin1'] Charset for part header parameters (e.g. filename) without an explicit one
*/

function allowAll (req, file, cb) {
cb(null, true)
}

/**
* @constructor
* @private
* @param {Options} options
*/
function Multer (options) {
if (options.storage) {
this.storage = options.storage
Expand Down Expand Up @@ -68,22 +133,54 @@ Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
return makeMiddleware(setup.bind(this))
}

/**
* Accept a single file for the field `name`. The file is stored in `req.file`.
*
* @param {string} name
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.single = function (name) {
return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE')
}

/**
* Accept an array of files for the field `name`, stored in `req.files`.
* Files skipped by `fileFilter` do not count towards `maxCount`.
*
* @param {string} name
* @param {number} [maxCount] Error with `LIMIT_UNEXPECTED_FILE` if more files are accepted
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.array = function (name, maxCount) {
return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY')
}

/**
* Accept a mix of files. `req.files` is an object keyed by field name, each
* value an array of files.
*
* @param {Array<{name: string, maxCount?: number}>} fields
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.fields = function (fields) {
return this._makeMiddleware(fields, 'OBJECT')
}

/**
* Accept only text fields. Any file results in a `LIMIT_UNEXPECTED_FILE` error.
*
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.none = function () {
return this._makeMiddleware([], 'NONE')
}

/**
* Accept all files, stored as an array in `req.files`. Only use this on routes
* that handle every uploaded file.
*
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.any = function () {
function setup () {
return {
Expand All @@ -99,6 +196,14 @@ Multer.prototype.any = function () {
return makeMiddleware(setup.bind(this))
}

/**
* Create a multer instance. Text fields are parsed into `req.body`; files go to
* `req.file` or `req.files` depending on the method used.
*
* @param {Options} [options]
* @returns {Multer}
* @throws {TypeError} If `options` is not an object
*/
function multer (options) {
if (options === undefined) {
return new Multer({})
Expand Down
17 changes: 17 additions & 0 deletions lib/multer-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ var errorMessages = {
INVALID_FIELD_NAME: 'Invalid field name'
}

/**
* Error raised by multer. Check `err.code` rather than the message.
*
* Codes: `LIMIT_PART_COUNT`, `LIMIT_FILE_SIZE`, `LIMIT_FILE_COUNT`,
* `LIMIT_FIELD_KEY`, `LIMIT_FIELD_VALUE`, `LIMIT_FIELD_COUNT`,
* `LIMIT_UNEXPECTED_FILE`, `MISSING_FIELD_NAME`, `LIMIT_FIELD_NESTING`,
* `LIMIT_FIELD_ARRAY_INDEX`, `INVALID_FIELD_NAME`, `STREAM_DESTROYED`.
*
* @constructor
* @extends Error
* @param {string} code One of the codes above
* @param {string} [field] Name of the field the error relates to
* @param {string} [filename] Client-supplied file name, for file errors
* @property {string} code
* @property {string} [field]
* @property {string} [filename]
*/
function MulterError (code, field, filename) {
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
Expand Down
18 changes: 18 additions & 0 deletions storage/disk.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ function getDestination (req, file, cb) {
cb(null, os.tmpdir())
}

/**
* Storage engine that writes files to disk.
*
* @constructor
* @private
* @param {Object} opts
* @param {string|function(Object, File, function(?Error, string=): void): void} [opts.destination]
* Folder to store files in, or a function that calls back with one. Defaults to `os.tmpdir()`
* @param {function(Object, File, function(?Error, string=): void): void} [opts.filename]
* Calls back with the file name to use. Defaults to a random hex name without extension
*/
function DiskStorage (opts) {
this.getFilename = (opts.filename || getFilename)

Expand Down Expand Up @@ -92,6 +103,13 @@ DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) {
outStream.destroy()
}

/**
* Create a disk storage engine. Sets `destination`, `filename` and `path` on
* the file object.
*
* @param {Object} opts See {@link DiskStorage}
* @returns {DiskStorage}
*/
module.exports = function (opts) {
return new DiskStorage(opts)
}
12 changes: 12 additions & 0 deletions storage/memory.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Storage engine that keeps files in memory.
*
* @constructor
* @private
*/
function MemoryStorage (opts) {}

MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) {
Expand All @@ -22,6 +28,12 @@ MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) {
cb(null)
}

/**
* Create a memory storage engine. Sets `buffer` on the file object with the
* whole file contents; set `limits.fileSize` to bound memory use.
*
* @returns {MemoryStorage}
*/
module.exports = function (opts) {
return new MemoryStorage(opts)
}