make-middleware.js 5.5 KB

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