var is = require('type-is') var Busboy = require('busboy') var extend = require('xtend') var onFinished = require('on-finished') 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', stream.read.bind(stream)) } 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) var busboy try { busboy = new 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) busboy.removeAllListeners() onFinished(req, function () { 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, fieldnameTruncated, valueTruncated) { if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME') if (fieldnameTruncated) 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('finish', function () { readFinished = true indicateDone() }) req.pipe(busboy) } } module.exports = makeMiddleware