make-middleware.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. var is = require('type-is')
  2. var Busboy = require('busboy')
  3. var extend = require('xtend')
  4. var appendField = require('append-field')
  5. var Counter = require('./counter')
  6. var MulterError = require('./multer-error')
  7. var FileAppender = require('./file-appender')
  8. var removeUploadedFiles = require('./remove-uploaded-files')
  9. function makeMiddleware (setup) {
  10. return function multerMiddleware (req, res, next) {
  11. if (!is(req, ['multipart'])) return next()
  12. var options = setup()
  13. var limits = options.limits
  14. var storage = options.storage
  15. var fileFilter = options.fileFilter
  16. var fileStrategy = options.fileStrategy
  17. var preservePath = options.preservePath
  18. req.body = Object.create(null)
  19. var busboy
  20. try {
  21. busboy = Busboy({ headers: req.headers, limits: limits, preservePath: preservePath })
  22. } catch (err) {
  23. return next(err)
  24. }
  25. var appender = new FileAppender(fileStrategy, req)
  26. var isDone = false
  27. var readFinished = false
  28. var errorOccured = false
  29. var pendingWrites = new Counter()
  30. var uploadedFiles = []
  31. function done (err) {
  32. if (isDone) return
  33. isDone = true
  34. req.unpipe(busboy)
  35. busboy.removeAllListeners()
  36. next(err)
  37. }
  38. function indicateDone () {
  39. if (readFinished && pendingWrites.isZero() && !errorOccured) done()
  40. }
  41. function abortWithError (uploadError) {
  42. if (errorOccured) return
  43. errorOccured = true
  44. pendingWrites.onceZero(function () {
  45. function remove (file, cb) {
  46. storage._removeFile(req, file, cb)
  47. }
  48. removeUploadedFiles(uploadedFiles, remove, function (err, storageErrors) {
  49. if (err) return done(err)
  50. uploadError.storageErrors = storageErrors
  51. done(uploadError)
  52. })
  53. })
  54. }
  55. function abortWithCode (code, optionalField) {
  56. abortWithError(new MulterError(code, optionalField))
  57. }
  58. // handle text field data
  59. busboy.on('field', function (fieldname, value, { nameTruncated, valueTruncated }) {
  60. if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
  61. if (nameTruncated) return abortWithCode('LIMIT_FIELD_KEY')
  62. if (valueTruncated) return abortWithCode('LIMIT_FIELD_VALUE', fieldname)
  63. // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
  64. if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
  65. if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY')
  66. }
  67. appendField(req.body, fieldname, value)
  68. })
  69. // handle files
  70. busboy.on('file', function (fieldname, fileStream, { filename, encoding, mimeType }) {
  71. // don't attach to the files object, if there is no file
  72. if (!filename) return fileStream.resume()
  73. // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
  74. if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
  75. if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY')
  76. }
  77. var file = {
  78. fieldname: fieldname,
  79. originalname: filename,
  80. encoding: encoding,
  81. mimetype: mimeType
  82. }
  83. var placeholder = appender.insertPlaceholder(file)
  84. fileFilter(req, file, function (err, includeFile) {
  85. if (err) {
  86. appender.removePlaceholder(placeholder)
  87. return abortWithError(err)
  88. }
  89. if (!includeFile) {
  90. appender.removePlaceholder(placeholder)
  91. return fileStream.resume()
  92. }
  93. var aborting = false
  94. pendingWrites.increment()
  95. Object.defineProperty(file, 'stream', {
  96. configurable: true,
  97. enumerable: false,
  98. value: fileStream
  99. })
  100. fileStream.on('error', function (err) {
  101. pendingWrites.decrement()
  102. abortWithError(err)
  103. })
  104. fileStream.on('limit', function () {
  105. aborting = true
  106. abortWithCode('LIMIT_FILE_SIZE', fieldname)
  107. })
  108. storage._handleFile(req, file, function (err, info) {
  109. if (aborting) {
  110. appender.removePlaceholder(placeholder)
  111. uploadedFiles.push(extend(file, info))
  112. return pendingWrites.decrement()
  113. }
  114. if (err) {
  115. appender.removePlaceholder(placeholder)
  116. pendingWrites.decrement()
  117. return abortWithError(err)
  118. }
  119. var fileInfo = extend(file, info)
  120. appender.replacePlaceholder(placeholder, fileInfo)
  121. uploadedFiles.push(fileInfo)
  122. pendingWrites.decrement()
  123. indicateDone()
  124. })
  125. })
  126. })
  127. busboy.on('error', function (err) { abortWithError(err) })
  128. busboy.on('partsLimit', function () { abortWithCode('LIMIT_PART_COUNT') })
  129. busboy.on('filesLimit', function () { abortWithCode('LIMIT_FILE_COUNT') })
  130. busboy.on('fieldsLimit', function () { abortWithCode('LIMIT_FIELD_COUNT') })
  131. busboy.on('close', function () {
  132. readFinished = true
  133. indicateDone()
  134. })
  135. req.pipe(busboy)
  136. }
  137. }
  138. module.exports = makeMiddleware