index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /*!
  2. * raw-body
  3. * Copyright(c) 2013-2014 Jonathan Ong
  4. * Copyright(c) 2014-2022 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. var asyncHooks = tryRequireAsyncHooks()
  13. var bytes = require('bytes')
  14. var createError = require('http-errors')
  15. var iconv = require('iconv-lite')
  16. var unpipe = require('unpipe')
  17. /**
  18. * Module exports.
  19. * @public
  20. */
  21. module.exports = getRawBody
  22. /**
  23. * Module variables.
  24. * @private
  25. */
  26. var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: /
  27. /**
  28. * Get the decoder for a given encoding.
  29. *
  30. * @param {string} encoding
  31. * @private
  32. */
  33. function getDecoder (encoding) {
  34. if (!encoding) return null
  35. try {
  36. return iconv.getDecoder(encoding)
  37. } catch (e) {
  38. // error getting decoder
  39. if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e
  40. // the encoding was not found
  41. throw createError(415, 'specified encoding unsupported', {
  42. encoding: encoding,
  43. type: 'encoding.unsupported'
  44. })
  45. }
  46. }
  47. /**
  48. * Get the raw body of a stream (typically HTTP).
  49. *
  50. * @param {object} stream
  51. * @param {object|string|function} [options]
  52. * @param {function} [callback]
  53. * @public
  54. */
  55. function getRawBody (stream, options, callback) {
  56. var done = callback
  57. var opts = options || {}
  58. if (options === true || typeof options === 'string') {
  59. // short cut for encoding
  60. opts = {
  61. encoding: options
  62. }
  63. }
  64. if (typeof options === 'function') {
  65. done = options
  66. opts = {}
  67. }
  68. // validate callback is a function, if provided
  69. if (done !== undefined && typeof done !== 'function') {
  70. throw new TypeError('argument callback must be a function')
  71. }
  72. // require the callback without promises
  73. if (!done && !global.Promise) {
  74. throw new TypeError('argument callback is required')
  75. }
  76. // get encoding
  77. var encoding = opts.encoding !== true
  78. ? opts.encoding
  79. : 'utf-8'
  80. // convert the limit to an integer
  81. var limit = bytes.parse(opts.limit)
  82. // convert the expected length to an integer
  83. var length = opts.length != null && !isNaN(opts.length)
  84. ? parseInt(opts.length, 10)
  85. : null
  86. if (done) {
  87. // classic callback style
  88. return readStream(stream, encoding, length, limit, wrap(done))
  89. }
  90. return new Promise(function executor (resolve, reject) {
  91. readStream(stream, encoding, length, limit, function onRead (err, buf) {
  92. if (err) return reject(err)
  93. resolve(buf)
  94. })
  95. })
  96. }
  97. /**
  98. * Halt a stream.
  99. *
  100. * @param {Object} stream
  101. * @private
  102. */
  103. function halt (stream) {
  104. // unpipe everything from the stream
  105. unpipe(stream)
  106. // pause stream
  107. if (typeof stream.pause === 'function') {
  108. stream.pause()
  109. }
  110. }
  111. /**
  112. * Read the data from the stream.
  113. *
  114. * @param {object} stream
  115. * @param {string} encoding
  116. * @param {number} length
  117. * @param {number} limit
  118. * @param {function} callback
  119. * @public
  120. */
  121. function readStream (stream, encoding, length, limit, callback) {
  122. var complete = false
  123. var sync = true
  124. // check the length and limit options.
  125. // note: we intentionally leave the stream paused,
  126. // so users should handle the stream themselves.
  127. if (limit !== null && length !== null && length > limit) {
  128. return done(createError(413, 'request entity too large', {
  129. expected: length,
  130. length: length,
  131. limit: limit,
  132. type: 'entity.too.large'
  133. }))
  134. }
  135. // streams1: assert request encoding is buffer.
  136. // streams2+: assert the stream encoding is buffer.
  137. // stream._decoder: streams1
  138. // state.encoding: streams2
  139. // state.decoder: streams2, specifically < 0.10.6
  140. var state = stream._readableState
  141. if (stream._decoder || (state && (state.encoding || state.decoder))) {
  142. // developer error
  143. return done(createError(500, 'stream encoding should not be set', {
  144. type: 'stream.encoding.set'
  145. }))
  146. }
  147. if (typeof stream.readable !== 'undefined' && !stream.readable) {
  148. return done(createError(500, 'stream is not readable', {
  149. type: 'stream.not.readable'
  150. }))
  151. }
  152. var received = 0
  153. var decoder
  154. try {
  155. decoder = getDecoder(encoding)
  156. } catch (err) {
  157. return done(err)
  158. }
  159. var buffer = decoder
  160. ? ''
  161. : []
  162. // attach listeners
  163. stream.on('aborted', onAborted)
  164. stream.on('close', cleanup)
  165. stream.on('data', onData)
  166. stream.on('end', onEnd)
  167. stream.on('error', onEnd)
  168. // mark sync section complete
  169. sync = false
  170. function done () {
  171. var args = new Array(arguments.length)
  172. // copy arguments
  173. for (var i = 0; i < args.length; i++) {
  174. args[i] = arguments[i]
  175. }
  176. // mark complete
  177. complete = true
  178. if (sync) {
  179. process.nextTick(invokeCallback)
  180. } else {
  181. invokeCallback()
  182. }
  183. function invokeCallback () {
  184. cleanup()
  185. if (args[0]) {
  186. // halt the stream on error
  187. halt(stream)
  188. }
  189. callback.apply(null, args)
  190. }
  191. }
  192. function onAborted () {
  193. if (complete) return
  194. done(createError(400, 'request aborted', {
  195. code: 'ECONNABORTED',
  196. expected: length,
  197. length: length,
  198. received: received,
  199. type: 'request.aborted'
  200. }))
  201. }
  202. function onData (chunk) {
  203. if (complete) return
  204. received += chunk.length
  205. if (limit !== null && received > limit) {
  206. done(createError(413, 'request entity too large', {
  207. limit: limit,
  208. received: received,
  209. type: 'entity.too.large'
  210. }))
  211. } else if (decoder) {
  212. buffer += decoder.write(chunk)
  213. } else {
  214. buffer.push(chunk)
  215. }
  216. }
  217. function onEnd (err) {
  218. if (complete) return
  219. if (err) return done(err)
  220. if (length !== null && received !== length) {
  221. done(createError(400, 'request size did not match content length', {
  222. expected: length,
  223. length: length,
  224. received: received,
  225. type: 'request.size.invalid'
  226. }))
  227. } else {
  228. var string = decoder
  229. ? buffer + (decoder.end() || '')
  230. : Buffer.concat(buffer)
  231. done(null, string)
  232. }
  233. }
  234. function cleanup () {
  235. buffer = null
  236. stream.removeListener('aborted', onAborted)
  237. stream.removeListener('data', onData)
  238. stream.removeListener('end', onEnd)
  239. stream.removeListener('error', onEnd)
  240. stream.removeListener('close', cleanup)
  241. }
  242. }
  243. /**
  244. * Try to require async_hooks
  245. * @private
  246. */
  247. function tryRequireAsyncHooks () {
  248. try {
  249. return require('async_hooks')
  250. } catch (e) {
  251. return {}
  252. }
  253. }
  254. /**
  255. * Wrap function with async resource, if possible.
  256. * AsyncResource.bind static method backported.
  257. * @private
  258. */
  259. function wrap (fn) {
  260. var res
  261. // create anonymous resource
  262. if (asyncHooks.AsyncResource) {
  263. res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')
  264. }
  265. // incompatible node.js
  266. if (!res || !res.runInAsyncScope) {
  267. return fn
  268. }
  269. // return bound function
  270. return res.runInAsyncScope.bind(res, fn, null)
  271. }