index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. var Busboy = require('busboy')
  2. var chan = require('chan')
  3. var BlackHoleStream = require('black-hole-stream')
  4. var inflate = require('inflation')
  5. var getDescriptor = Object.getOwnPropertyDescriptor
  6. var isArray = Array.isArray
  7. module.exports = function (request, options) {
  8. var ch = chan()
  9. var parts = function (fn) {
  10. if (fn) return ch(fn)
  11. return new Promise(function (resolve, reject) {
  12. ch(function (err, res) {
  13. if (err) return reject(err)
  14. resolve(res)
  15. })
  16. })
  17. }
  18. // koa special sauce
  19. request = request.req || request
  20. options = options || {}
  21. options.headers = request.headers
  22. // options.checkField hook `function(name, val, fieldnameTruncated, valTruncated)`
  23. // options.checkFile hook `function(fieldname, fileStream, filename, encoding, mimetype)`
  24. var checkField = options.checkField
  25. var checkFile = options.checkFile
  26. var lastError
  27. var busboy = new Busboy(options)
  28. request = inflate(request)
  29. request.on('close', cleanup)
  30. busboy
  31. .on('field', onField)
  32. .on('file', onFile)
  33. .on('close', cleanup)
  34. .on('error', onEnd)
  35. .on('finish', onEnd)
  36. busboy.on('partsLimit', function(){
  37. var err = new Error('Reach parts limit')
  38. err.code = 'Request_parts_limit'
  39. err.status = 413
  40. onError(err)
  41. })
  42. busboy.on('filesLimit', function(){
  43. var err = new Error('Reach files limit')
  44. err.code = 'Request_files_limit'
  45. err.status = 413
  46. onError(err)
  47. })
  48. busboy.on('fieldsLimit', function(){
  49. var err = new Error('Reach fields limit')
  50. err.code = 'Request_fields_limit'
  51. err.status = 413
  52. onError(err)
  53. })
  54. request.pipe(busboy)
  55. // i would just put everything in an array
  56. // but people will complain
  57. if (options.autoFields) {
  58. var field = parts.field = {} // object lookup
  59. var fields = parts.fields = [] // list lookup
  60. }
  61. return parts
  62. function onField(name, val, fieldnameTruncated, valTruncated) {
  63. if (checkField) {
  64. var err = checkField(name, val, fieldnameTruncated, valTruncated)
  65. if (err) {
  66. return onError(err)
  67. }
  68. }
  69. var args = [name, val, fieldnameTruncated, valTruncated]
  70. if (options.autoFields) {
  71. fields.push(args)
  72. // don't overwrite prototypes
  73. if (getDescriptor(Object.prototype, name)) return
  74. var prev = field[name]
  75. if (prev == null) return field[name] = val
  76. if (isArray(prev)) return prev.push(val)
  77. field[name] = [prev, val]
  78. } else {
  79. ch(args)
  80. }
  81. }
  82. function onFile(fieldname, file, filename, encoding, mimetype) {
  83. if (checkFile) {
  84. var err = checkFile(fieldname, file, filename, encoding, mimetype)
  85. if (err) {
  86. // make sure request stream's data has been read
  87. var blackHoleStream = new BlackHoleStream()
  88. file.pipe(blackHoleStream)
  89. return onError(err)
  90. }
  91. }
  92. // opinionated, but 5 arguments is ridiculous
  93. file.fieldname = fieldname
  94. file.filename = filename
  95. file.transferEncoding = file.encoding = encoding
  96. file.mimeType = file.mime = mimetype
  97. ch(file)
  98. }
  99. function onError(err) {
  100. lastError = err
  101. }
  102. function onEnd() {
  103. cleanup()
  104. busboy.removeListener('finish', onEnd)
  105. busboy.removeListener('error', onEnd)
  106. ch(lastError)
  107. }
  108. function cleanup() {
  109. // keep finish listener to wait all data flushed
  110. // keep error listener to wait stream error
  111. request.removeListener('close', cleanup)
  112. busboy.removeListener('field', onField)
  113. busboy.removeListener('file', onFile)
  114. busboy.removeListener('close', cleanup)
  115. }
  116. }