processRequest.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. 'use strict'
  2. exports.__esModule = true
  3. exports.processRequest = void 0
  4. var _util = _interopRequireDefault(require('util'))
  5. var _busboy = _interopRequireDefault(require('busboy'))
  6. var _fsCapacitor = require('fs-capacitor')
  7. var _httpErrors = _interopRequireDefault(require('http-errors'))
  8. var _objectPath = _interopRequireDefault(require('object-path'))
  9. var _constants = require('./constants')
  10. var _ignoreStream = require('./ignoreStream')
  11. var _isEnumerableObject = require('./isEnumerableObject')
  12. // istanbul ignore next
  13. function _interopRequireDefault(obj) {
  14. return obj && obj.__esModule ? obj : { default: obj }
  15. }
  16. class Upload {
  17. constructor() {
  18. this.promise = new Promise((resolve, reject) => {
  19. this.resolve = file => {
  20. this.file = file
  21. resolve(file)
  22. }
  23. this.reject = reject
  24. })
  25. this.promise.catch(() => {})
  26. }
  27. }
  28. const processRequest = (
  29. request,
  30. response,
  31. { maxFieldSize = 1000000, maxFileSize = Infinity, maxFiles = Infinity } = {}
  32. ) =>
  33. new Promise((resolve, reject) => {
  34. let released
  35. let exitError
  36. let currentStream
  37. let operations
  38. let operationsPath
  39. let map
  40. const parser = new _busboy.default({
  41. headers: request.headers,
  42. limits: {
  43. fieldSize: maxFieldSize,
  44. fields: 2,
  45. fileSize: maxFileSize,
  46. files: maxFiles
  47. }
  48. })
  49. const exit = error => {
  50. if (exitError) return
  51. exitError = error
  52. reject(exitError)
  53. parser.destroy()
  54. if (currentStream) currentStream.destroy(exitError)
  55. if (map)
  56. for (const upload of map.values())
  57. if (!upload.file) upload.reject(exitError)
  58. request.unpipe(parser)
  59. setImmediate(() => {
  60. request.resume()
  61. })
  62. }
  63. const release = () => {
  64. // istanbul ignore next
  65. if (released) return
  66. released = true
  67. if (map)
  68. for (const upload of map.values())
  69. if (upload.file) upload.file.capacitor.destroy()
  70. }
  71. const abort = () => {
  72. exit(
  73. (0, _httpErrors.default)(
  74. 499,
  75. 'Request disconnected during file upload stream parsing.'
  76. )
  77. )
  78. }
  79. parser.on(
  80. 'field',
  81. (fieldName, value, fieldNameTruncated, valueTruncated) => {
  82. if (exitError) return
  83. if (valueTruncated)
  84. return exit(
  85. (0, _httpErrors.default)(
  86. 413,
  87. `The ‘${fieldName}’ multipart field value exceeds the ${maxFieldSize} byte size limit.`
  88. )
  89. )
  90. switch (fieldName) {
  91. case 'operations':
  92. try {
  93. operations = JSON.parse(value)
  94. } catch (error) {
  95. return exit(
  96. (0, _httpErrors.default)(
  97. 400,
  98. `Invalid JSON in the ‘operations’ multipart field (${_constants.SPEC_URL}).`
  99. )
  100. )
  101. }
  102. if (
  103. !(0, _isEnumerableObject.isEnumerableObject)(operations) &&
  104. !Array.isArray(operations)
  105. )
  106. return exit(
  107. (0, _httpErrors.default)(
  108. 400,
  109. `Invalid type for the ‘operations’ multipart field (${_constants.SPEC_URL}).`
  110. )
  111. )
  112. operationsPath = (0, _objectPath.default)(operations)
  113. break
  114. case 'map': {
  115. if (!operations)
  116. return exit(
  117. (0, _httpErrors.default)(
  118. 400,
  119. `Misordered multipart fields; ‘map’ should follow ‘operations’ (${_constants.SPEC_URL}).`
  120. )
  121. )
  122. let parsedMap
  123. try {
  124. parsedMap = JSON.parse(value)
  125. } catch (error) {
  126. return exit(
  127. (0, _httpErrors.default)(
  128. 400,
  129. `Invalid JSON in the ‘map’ multipart field (${_constants.SPEC_URL}).`
  130. )
  131. )
  132. }
  133. if (!(0, _isEnumerableObject.isEnumerableObject)(parsedMap))
  134. return exit(
  135. (0, _httpErrors.default)(
  136. 400,
  137. `Invalid type for the ‘map’ multipart field (${_constants.SPEC_URL}).`
  138. )
  139. )
  140. const mapEntries = Object.entries(parsedMap)
  141. if (mapEntries.length > maxFiles)
  142. return exit(
  143. (0, _httpErrors.default)(
  144. 413,
  145. `${maxFiles} max file uploads exceeded.`
  146. )
  147. )
  148. map = new Map()
  149. for (const [fieldName, paths] of mapEntries) {
  150. if (!Array.isArray(paths))
  151. return exit(
  152. (0, _httpErrors.default)(
  153. 400,
  154. `Invalid type for the ‘map’ multipart field entry key ‘${fieldName}’ array (${_constants.SPEC_URL}).`
  155. )
  156. )
  157. map.set(fieldName, new Upload())
  158. for (const [index, path] of paths.entries()) {
  159. if (typeof path !== 'string')
  160. return exit(
  161. (0, _httpErrors.default)(
  162. 400,
  163. `Invalid type for the ‘map’ multipart field entry key ‘${fieldName}’ array index ‘${index}’ value (${_constants.SPEC_URL}).`
  164. )
  165. )
  166. try {
  167. operationsPath.set(path, map.get(fieldName).promise)
  168. } catch (error) {
  169. return exit(
  170. (0, _httpErrors.default)(
  171. 400,
  172. `Invalid object path for the ‘map’ multipart field entry key ‘${fieldName}’ array index ‘${index}’ value ‘${path}’ (${_constants.SPEC_URL}).`
  173. )
  174. )
  175. }
  176. }
  177. }
  178. resolve(operations)
  179. }
  180. }
  181. }
  182. )
  183. parser.on('file', (fieldName, stream, filename, encoding, mimetype) => {
  184. if (exitError) {
  185. ;(0, _ignoreStream.ignoreStream)(stream)
  186. return
  187. }
  188. if (!map) {
  189. ;(0, _ignoreStream.ignoreStream)(stream)
  190. return exit(
  191. (0, _httpErrors.default)(
  192. 400,
  193. `Misordered multipart fields; files should follow ‘map’ (${_constants.SPEC_URL}).`
  194. )
  195. )
  196. }
  197. currentStream = stream
  198. stream.on('end', () => {
  199. currentStream = null
  200. })
  201. const upload = map.get(fieldName)
  202. if (!upload) {
  203. ;(0, _ignoreStream.ignoreStream)(stream)
  204. return
  205. }
  206. const capacitor = new _fsCapacitor.WriteStream()
  207. capacitor.on('error', () => {
  208. stream.unpipe()
  209. stream.resume()
  210. })
  211. stream.on('limit', () => {
  212. stream.unpipe()
  213. capacitor.destroy(
  214. (0, _httpErrors.default)(
  215. 413,
  216. `File truncated as it exceeds the ${maxFileSize} byte size limit.`
  217. )
  218. )
  219. })
  220. stream.on('error', error => {
  221. stream.unpipe() // istanbul ignore next
  222. capacitor.destroy(exitError || error)
  223. })
  224. stream.pipe(capacitor)
  225. const file = {
  226. filename,
  227. mimetype,
  228. encoding,
  229. createReadStream() {
  230. const error = capacitor.error || (released ? exitError : null)
  231. if (error) throw error
  232. return capacitor.createReadStream()
  233. }
  234. }
  235. let capacitorStream
  236. Object.defineProperty(file, 'stream', {
  237. get: _util.default.deprecate(function() {
  238. if (!capacitorStream) capacitorStream = this.createReadStream()
  239. return capacitorStream
  240. }, 'File upload property ‘stream’ is deprecated. Use ‘createReadStream()’ instead.')
  241. })
  242. Object.defineProperty(file, 'capacitor', {
  243. value: capacitor
  244. })
  245. upload.resolve(file)
  246. })
  247. parser.once('filesLimit', () =>
  248. exit(
  249. (0, _httpErrors.default)(413, `${maxFiles} max file uploads exceeded.`)
  250. )
  251. )
  252. parser.once('finish', () => {
  253. request.unpipe(parser)
  254. request.resume()
  255. if (!operations)
  256. return exit(
  257. (0, _httpErrors.default)(
  258. 400,
  259. `Missing multipart field ‘operations’ (${_constants.SPEC_URL}).`
  260. )
  261. )
  262. if (!map)
  263. return exit(
  264. (0, _httpErrors.default)(
  265. 400,
  266. `Missing multipart field ‘map’ (${_constants.SPEC_URL}).`
  267. )
  268. )
  269. for (const upload of map.values())
  270. if (!upload.file)
  271. upload.reject(
  272. (0, _httpErrors.default)(400, 'File missing in the request.')
  273. )
  274. })
  275. parser.once('error', exit)
  276. response.once('finish', release)
  277. response.once('close', release)
  278. request.once('close', abort)
  279. request.once('end', () => {
  280. request.removeListener('close', abort)
  281. })
  282. request.pipe(parser)
  283. })
  284. exports.processRequest = processRequest