index.js 5.9 KB

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