You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.2 KiB
182 lines
5.2 KiB
1 month ago
|
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
|