Comparing version 2.0.0-alpha.7 to 2.0.0-beta.1
@@ -6,2 +6,6 @@ # Change log | ||
## 2.0.0-beta.1 - 2019-11-23 | ||
- Breaking: drop support for Node.js < 8.3.x | ||
## 2.0.0-alpha.7 - 2019-05-03 | ||
@@ -8,0 +12,0 @@ |
63
index.js
@@ -5,49 +5,44 @@ const createFileFilter = require('./lib/file-filter') | ||
function _middleware (limits, fields, fileStrategy) { | ||
return createMiddleware(function setup () { | ||
return { | ||
fields: fields, | ||
limits: limits, | ||
fileFilter: createFileFilter(fields), | ||
fileStrategy: fileStrategy | ||
} | ||
}) | ||
return createMiddleware(() => ({ | ||
fields: fields, | ||
limits: limits, | ||
fileFilter: createFileFilter(fields), | ||
fileStrategy: fileStrategy | ||
})) | ||
} | ||
function Multer (options) { | ||
this.limits = options.limits | ||
} | ||
class Multer { | ||
constructor (options) { | ||
this.limits = options.limits | ||
} | ||
Multer.prototype.single = function (name) { | ||
return _middleware(this.limits, [{ name: name, maxCount: 1 }], 'VALUE') | ||
} | ||
single (name) { | ||
return _middleware(this.limits, [{ name: name, maxCount: 1 }], 'VALUE') | ||
} | ||
Multer.prototype.array = function (name, maxCount) { | ||
return _middleware(this.limits, [{ name: name, maxCount: maxCount }], 'ARRAY') | ||
} | ||
array (name, maxCount) { | ||
return _middleware(this.limits, [{ name: name, maxCount: maxCount }], 'ARRAY') | ||
} | ||
Multer.prototype.fields = function (fields) { | ||
return _middleware(this.limits, fields, 'OBJECT') | ||
} | ||
fields (fields) { | ||
return _middleware(this.limits, fields, 'OBJECT') | ||
} | ||
Multer.prototype.none = function () { | ||
return _middleware(this.limits, [], 'NONE') | ||
} | ||
none () { | ||
return _middleware(this.limits, [], 'NONE') | ||
} | ||
Multer.prototype.any = function () { | ||
function setup () { | ||
return { | ||
any () { | ||
return createMiddleware(() => ({ | ||
fields: [], | ||
limits: this.limits, | ||
fileFilter: function () {}, | ||
fileFilter: () => {}, | ||
fileStrategy: 'ARRAY' | ||
} | ||
})) | ||
} | ||
return createMiddleware(setup.bind(this)) | ||
} | ||
function multer (options) { | ||
if (options === undefined) options = {} | ||
if (options === null) throw new TypeError('Expected object for arugment "options", got null') | ||
if (typeof options !== 'object') throw new TypeError('Expected object for arugment "options", got ' + (typeof options)) | ||
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}`) | ||
@@ -54,0 +49,0 @@ if (options.dest || options.storage || options.fileFilter) { |
@@ -1,3 +0,1 @@ | ||
const util = require('util') | ||
const errorMessages = new Map([ | ||
@@ -14,15 +12,14 @@ ['CLIENT_ABORTED', 'Client aborted'], | ||
function MulterError (code, optionalField) { | ||
Error.captureStackTrace(this, this.constructor) | ||
this.name = this.constructor.name | ||
this.message = errorMessages.get(code) | ||
this.code = code | ||
class MulterError extends Error { | ||
constructor (code, optionalField) { | ||
super(errorMessages.get(code)) | ||
if (optionalField) { | ||
this.field = optionalField | ||
this.code = code | ||
this.name = this.constructor.name | ||
if (optionalField) this.field = optionalField | ||
Error.captureStackTrace(this, this.constructor) | ||
} | ||
} | ||
util.inherits(MulterError, Error) | ||
module.exports = MulterError |
@@ -7,9 +7,9 @@ function createFileAppender (strategy, req, fields) { | ||
case 'OBJECT': req.files = Object.create(null); break | ||
default: throw new Error('Unknown file strategy: ' + strategy) | ||
default: throw new Error(`Unknown file strategy: ${strategy}`) | ||
} | ||
if (strategy === 'OBJECT') { | ||
fields.forEach(function (field) { | ||
for (const field of fields) { | ||
req.files[field.name] = [] | ||
}) | ||
} | ||
} | ||
@@ -16,0 +16,0 @@ |
const MulterError = require('./error') | ||
module.exports = function createFileFilter (fields) { | ||
function createFileFilter (fields) { | ||
const filesLeft = new Map() | ||
fields.forEach(function (field) { | ||
for (const field of fields) { | ||
if (typeof field.maxCount === 'number') { | ||
@@ -12,3 +12,3 @@ filesLeft.set(field.name, field.maxCount) | ||
} | ||
}) | ||
} | ||
@@ -29,1 +29,3 @@ return function fileFilter (file) { | ||
} | ||
module.exports = createFileFilter |
@@ -8,32 +8,29 @@ const is = require('type-is') | ||
module.exports = function createMiddleware (setup) { | ||
return function multerMiddleware (req, res, next) { | ||
if (!is(req, ['multipart'])) return next() | ||
async function handleRequest (setup, req) { | ||
const options = setup() | ||
const result = await readBody(req, options.limits, options.fileFilter) | ||
const options = setup() | ||
req.body = Object.create(null) | ||
readBody(req, options.limits, options.fileFilter) | ||
.then(function (result) { | ||
req.body = Object.create(null) | ||
for (const field of result.fields) { | ||
appendField(req.body, field.key, field.value) | ||
} | ||
result.fields.forEach(function (field) { | ||
appendField(req.body, field.key, field.value) | ||
}) | ||
const appendFile = createFileAppender(options.fileStrategy, req, options.fields) | ||
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, () => {})) | ||
result.files.forEach(function (file) { | ||
file.stream = fs.createReadStream(file.path) | ||
appendFile(file) | ||
} | ||
} | ||
file.stream.on('open', function () { | ||
fs.unlink(file.path, function () {}) | ||
}) | ||
appendFile(file) | ||
}) | ||
next() | ||
}) | ||
.catch(next) | ||
function createMiddleware (setup) { | ||
return function multerMiddleware (req, res, next) { | ||
if (!is(req, ['multipart'])) return next() | ||
handleRequest(setup, req).then(next, next) | ||
} | ||
} | ||
module.exports = createMiddleware |
@@ -17,6 +17,6 @@ const path = require('path') | ||
function collectFields (busboy, limits) { | ||
return new Promise(function (resolve, reject) { | ||
return new Promise((resolve, reject) => { | ||
const result = [] | ||
busboy.on('field', function (fieldname, value, fieldnameTruncated, valueTruncated) { | ||
busboy.on('field', (fieldname, value, fieldnameTruncated, valueTruncated) => { | ||
if (fieldnameTruncated) return reject(new MulterError('LIMIT_FIELD_KEY')) | ||
@@ -33,5 +33,3 @@ if (valueTruncated) return reject(new MulterError('LIMIT_FIELD_VALUE', fieldname)) | ||
busboy.on('finish', function () { | ||
resolve(result) | ||
}) | ||
busboy.on('finish', () => resolve(result)) | ||
}) | ||
@@ -41,9 +39,14 @@ } | ||
function collectFiles (busboy, limits, fileFilter) { | ||
return new Promise(function (resolve, reject) { | ||
return new Promise((resolve, reject) => { | ||
const result = [] | ||
busboy.on('file', function (fieldname, fileStream, filename, encoding, mimetype) { | ||
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) | ||
@@ -61,68 +64,46 @@ if (limits && limits.hasOwnProperty('fieldNameSize')) { | ||
const limitHit = new Promise(function (resolve) { | ||
fileStream.on('limit', resolve) | ||
}) | ||
try { | ||
fileFilter(file) | ||
} catch (err) { | ||
return reject(err) | ||
} | ||
Promise.resolve() | ||
.then(function () { | ||
return fileFilter(file) | ||
}) | ||
.then(function () { | ||
limitHit.then(function () { | ||
reject(new MulterError('LIMIT_FILE_SIZE', fieldname)) | ||
}) | ||
const target = temp.createWriteStream() | ||
const detector = new FileType() | ||
const fileClosed = new Promise((resolve) => target.on('close', resolve)) | ||
const target = temp.createWriteStream() | ||
const detector = new FileType() | ||
const promise = pump(fileStream, detector, target) | ||
.then(async () => { | ||
await fileClosed | ||
file.path = target.path | ||
file.size = target.bytesWritten | ||
const fileClosed = new Promise(function (resolve) { | ||
target.on('close', resolve) | ||
}) | ||
const fileType = await detector.fileTypePromise() | ||
file.detectedMimeType = (fileType ? fileType.mime : null) | ||
file.detectedFileExtension = (fileType ? `.${fileType.ext}` : '') | ||
const promise = pump(fileStream, detector, target) | ||
.then(function () { | ||
return fileClosed | ||
}) | ||
.then(function () { | ||
return detector.fileTypePromise() | ||
}) | ||
.then(function (fileType) { | ||
file.path = target.path | ||
file.size = target.bytesWritten | ||
file.detectedMimeType = (fileType ? fileType.mime : null) | ||
file.detectedFileExtension = (fileType ? '.' + fileType.ext : '') | ||
return file | ||
}) | ||
.catch(reject) | ||
result.push(promise) | ||
return file | ||
}) | ||
.catch(reject) | ||
result.push(promise) | ||
}) | ||
busboy.on('finish', function () { | ||
resolve(Promise.all(result)) | ||
}) | ||
busboy.on('finish', () => resolve(Promise.all(result))) | ||
}) | ||
} | ||
function readBody (req, limits, fileFilter) { | ||
let busboy | ||
async function readBody (req, limits, fileFilter) { | ||
const busboy = new Busboy({ headers: req.headers, limits: limits }) | ||
try { | ||
busboy = new Busboy({ headers: req.headers, limits: limits }) | ||
} catch (err) { | ||
return Promise.reject(err) | ||
} | ||
const fields = collectFields(busboy, limits) | ||
const files = collectFiles(busboy, limits, fileFilter) | ||
const guard = new Promise(function (resolve, reject) { | ||
req.on('error', function (err) { reject(err) }) | ||
busboy.on('error', function (err) { reject(err) }) | ||
const guard = new Promise((resolve, reject) => { | ||
req.on('error', (err) => reject(err)) | ||
busboy.on('error', (err) => reject(err)) | ||
req.on('aborted', function () { reject(new MulterError('CLIENT_ABORTED')) }) | ||
busboy.on('partsLimit', function () { reject(new MulterError('LIMIT_PART_COUNT')) }) | ||
busboy.on('filesLimit', function () { reject(new MulterError('LIMIT_FILE_COUNT')) }) | ||
busboy.on('fieldsLimit', function () { reject(new MulterError('LIMIT_FIELD_COUNT')) }) | ||
req.on('aborted', () => reject(new MulterError('CLIENT_ABORTED'))) | ||
busboy.on('partsLimit', () => reject(new MulterError('LIMIT_PART_COUNT'))) | ||
busboy.on('filesLimit', () => reject(new MulterError('LIMIT_FILE_COUNT'))) | ||
busboy.on('fieldsLimit', () => reject(new MulterError('LIMIT_FIELD_COUNT'))) | ||
@@ -134,18 +115,17 @@ busboy.on('finish', resolve) | ||
return Promise.all([fields, files, guard]) | ||
.then(function (result) { | ||
return { fields: result[0], files: result[1] } | ||
}) | ||
.catch(function (err) { | ||
req.unpipe(busboy) | ||
drainStream(req) | ||
busboy.removeAllListeners() | ||
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() | ||
return onFinished(req).then( | ||
function () { throw err }, | ||
function () { throw err } | ||
) | ||
}) | ||
// Wait for request to close, finish, or error | ||
await onFinished(req).catch(() => {}) | ||
throw err | ||
} | ||
} | ||
module.exports = readBody |
{ | ||
"name": "multer", | ||
"description": "Middleware for handling `multipart/form-data`.", | ||
"version": "2.0.0-alpha.7", | ||
"version": "2.0.0-beta.1", | ||
"contributors": [ | ||
@@ -28,3 +28,3 @@ "Hage Yaapa <captain@hacksparrow.com> (http://www.hacksparrow.com)", | ||
"pump": "^3.0.0", | ||
"stream-file-type": "^0.3.2", | ||
"stream-file-type": "^0.4.0", | ||
"type-is": "^1.6.18" | ||
@@ -35,5 +35,5 @@ }, | ||
"express": "^4.16.4", | ||
"form-data": "^2.1.0", | ||
"get-stream": "^4.1.0", | ||
"hasha": "^4.0.1", | ||
"form-data": "^3.0.0", | ||
"get-stream": "^5.1.0", | ||
"hasha": "^5.1.0", | ||
"mocha": "^6.1.4", | ||
@@ -45,3 +45,3 @@ "recursive-nullify": "^1.0.0", | ||
"engines": { | ||
"node": ">= 6.0.0" | ||
"node": ">=8.3" | ||
}, | ||
@@ -48,0 +48,0 @@ "files": [ |
@@ -27,3 +27,3 @@ # 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) | ||
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 | ||
@@ -33,3 +33,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 | ||
@@ -40,3 +40,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 | ||
@@ -61,3 +61,3 @@ // | ||
app.post('/profile', upload.none(), function (req, res, next) { | ||
app.post('/profile', upload.none(), (req, res, next) => { | ||
// req.body contains the text fields | ||
@@ -165,4 +165,4 @@ }) | ||
app.post('/profile', function (req, res) { | ||
upload(req, res, function (err) { | ||
app.post('/profile', (req, res) => { | ||
upload(req, res, (err) => { | ||
if (err) { | ||
@@ -169,0 +169,0 @@ // An error occurred when uploading |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18204
232
+ Addedfile-type@12.4.2(transitive)
+ Addedstream-file-type@0.4.0(transitive)
- Removedfile-type@10.11.0(transitive)
- Removedstream-file-type@0.3.2(transitive)
Updatedstream-file-type@^0.4.0