read.js 4.2 KB

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