make-middleware.js 5.2 KB

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