read.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*!
  2. * body-parser
  3. * Copyright(c) 2014-2015 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. 'use strict'
  7. /**
  8. * Module dependencies.
  9. * @private
  10. */
  11. var createError = require('http-errors')
  12. var getBody = require('raw-body')
  13. var iconv = require('iconv-lite')
  14. var onFinished = require('on-finished')
  15. var zlib = require('node:zlib')
  16. /**
  17. * Module exports.
  18. */
  19. module.exports = read
  20. /**
  21. * Read a request into a buffer and parse.
  22. *
  23. * @param {object} req
  24. * @param {object} res
  25. * @param {function} next
  26. * @param {function} parse
  27. * @param {function} debug
  28. * @param {object} options
  29. * @private
  30. */
  31. function read (req, res, next, parse, debug, options) {
  32. var length
  33. var opts = options
  34. var stream
  35. // read options
  36. var encoding = opts.encoding !== null
  37. ? opts.encoding
  38. : null
  39. var verify = opts.verify
  40. try {
  41. // get the content stream
  42. stream = contentstream(req, debug, opts.inflate)
  43. length = stream.length
  44. stream.length = undefined
  45. } catch (err) {
  46. return next(err)
  47. }
  48. // set raw-body options
  49. opts.length = length
  50. opts.encoding = verify
  51. ? null
  52. : encoding
  53. // assert charset is supported
  54. if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
  55. return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
  56. charset: encoding.toLowerCase(),
  57. type: 'charset.unsupported'
  58. }))
  59. }
  60. // read body
  61. debug('read body')
  62. getBody(stream, opts, function (error, body) {
  63. if (error) {
  64. var _error
  65. if (error.type === 'encoding.unsupported') {
  66. // echo back charset
  67. _error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
  68. charset: encoding.toLowerCase(),
  69. type: 'charset.unsupported'
  70. })
  71. } else {
  72. // set status code on error
  73. _error = createError(400, error)
  74. }
  75. // unpipe from stream and destroy
  76. if (stream !== req) {
  77. req.unpipe()
  78. stream.destroy()
  79. }
  80. // read off entire request
  81. dump(req, function onfinished () {
  82. next(createError(400, _error))
  83. })
  84. return
  85. }
  86. // verify
  87. if (verify) {
  88. try {
  89. debug('verify body')
  90. verify(req, res, body, encoding)
  91. } catch (err) {
  92. next(createError(403, err, {
  93. body: body,
  94. type: err.type || 'entity.verify.failed'
  95. }))
  96. return
  97. }
  98. }
  99. // parse
  100. var str = body
  101. try {
  102. debug('parse body')
  103. str = typeof body !== 'string' && encoding !== null
  104. ? iconv.decode(body, encoding)
  105. : body
  106. req.body = parse(str, encoding)
  107. } catch (err) {
  108. next(createError(400, err, {
  109. body: str,
  110. type: err.type || 'entity.parse.failed'
  111. }))
  112. return
  113. }
  114. next()
  115. })
  116. }
  117. /**
  118. * Get the content stream of the request.
  119. *
  120. * @param {object} req
  121. * @param {function} debug
  122. * @param {boolean} [inflate=true]
  123. * @return {object}
  124. * @api private
  125. */
  126. function contentstream (req, debug, inflate) {
  127. var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
  128. var length = req.headers['content-length']
  129. debug('content-encoding "%s"', encoding)
  130. if (inflate === false && encoding !== 'identity') {
  131. throw createError(415, 'content encoding unsupported', {
  132. encoding: encoding,
  133. type: 'encoding.unsupported'
  134. })
  135. }
  136. if (encoding === 'identity') {
  137. req.length = length
  138. return req
  139. }
  140. var stream = createDecompressionStream(encoding, debug)
  141. req.pipe(stream)
  142. return stream
  143. }
  144. /**
  145. * Create a decompression stream for the given encoding.
  146. * @param {string} encoding
  147. * @param {function} debug
  148. * @return {object}
  149. * @api private
  150. */
  151. function createDecompressionStream (encoding, debug) {
  152. switch (encoding) {
  153. case 'deflate':
  154. debug('inflate body')
  155. return zlib.createInflate()
  156. case 'gzip':
  157. debug('gunzip body')
  158. return zlib.createGunzip()
  159. case 'br':
  160. debug('brotli decompress body')
  161. return zlib.createBrotliDecompress()
  162. default:
  163. throw createError(415, 'unsupported content encoding "' + encoding + '"', {
  164. encoding: encoding,
  165. type: 'encoding.unsupported'
  166. })
  167. }
  168. }
  169. /**
  170. * Dump the contents of a request.
  171. *
  172. * @param {object} req
  173. * @param {function} callback
  174. * @api private
  175. */
  176. function dump (req, callback) {
  177. if (onFinished.isFinished(req)) {
  178. callback(null)
  179. } else {
  180. onFinished(req, callback)
  181. req.resume()
  182. }
  183. }