| 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 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 = 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) |
| 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 |