Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. // light validation
  59. if (stream === undefined) {
  60. throw new TypeError('argument stream is required')
  61. } else if (typeof stream !== 'object' || stream === null || typeof stream.on !== 'function') {
  62. throw new TypeError('argument stream must be a stream')
  63. }
  64. if (options === true || typeof options === 'string') {
  65. // short cut for encoding
  66. opts = {
  67. encoding: options
  68. }
  69. }
  70. if (typeof options === 'function') {
  71. done = options
  72. opts = {}
  73. }
  74. // validate callback is a function, if provided
  75. if (done !== undefined && typeof done !== 'function') {
  76. throw new TypeError('argument callback must be a function')
  77. }
  78. // require the callback without promises
  79. if (!done && !global.Promise) {
  80. throw new TypeError('argument callback is required')
  81. }
  82. // get encoding
  83. var encoding = opts.encoding !== true
  84. ? opts.encoding
  85. : 'utf-8'
  86. // convert the limit to an integer
  87. var limit = bytes.parse(opts.limit)
  88. // convert the expected length to an integer
  89. var length = opts.length != null && !isNaN(opts.length)
  90. ? parseInt(opts.length, 10)
  91. : null
  92. if (done) {
  93. // classic callback style
  94. return readStream(stream, encoding, length, limit, wrap(done))
  95. }
  96. return new Promise(function executor (resolve, reject) {
  97. readStream(stream, encoding, length, limit, function onRead (err, buf) {
  98. if (err) return reject(err)
  99. resolve(buf)
  100. })
  101. })
  102. }
  103. /**
  104. * Halt a stream.
  105. *
  106. * @param {Object} stream
  107. * @private
  108. */
  109. function halt (stream) {
  110. // unpipe everything from the stream
  111. unpipe(stream)
  112. // pause stream
  113. if (typeof stream.pause === 'function') {
  114. stream.pause()
  115. }
  116. }
  117. /**
  118. * Read the data from the stream.
  119. *
  120. * @param {object} stream
  121. * @param {string} encoding
  122. * @param {number} length
  123. * @param {number} limit
  124. * @param {function} callback
  125. * @public
  126. */
  127. function readStream (stream, encoding, length, limit, callback) {
  128. var complete = false
  129. var sync = true
  130. // check the length and limit options.
  131. // note: we intentionally leave the stream paused,
  132. // so users should handle the stream themselves.
  133. if (limit !== null && length !== null && length > limit) {
  134. return done(createError(413, 'request entity too large', {
  135. expected: length,
  136. length: length,
  137. limit: limit,
  138. type: 'entity.too.large'
  139. }))
  140. }
  141. // streams1: assert request encoding is buffer.
  142. // streams2+: assert the stream encoding is buffer.
  143. // stream._decoder: streams1
  144. // state.encoding: streams2
  145. // state.decoder: streams2, specifically < 0.10.6
  146. var state = stream._readableState
  147. if (stream._decoder || (state && (state.encoding || state.decoder))) {
  148. // developer error
  149. return done(createError(500, 'stream encoding should not be set', {
  150. type: 'stream.encoding.set'
  151. }))
  152. }
  153. if (typeof stream.readable !== 'undefined' && !stream.readable) {
  154. return done(createError(500, 'stream is not readable', {
  155. type: 'stream.not.readable'
  156. }))
  157. }
  158. var received = 0
  159. var decoder
  160. try {
  161. decoder = getDecoder(encoding)
  162. } catch (err) {
  163. return done(err)
  164. }
  165. var buffer = decoder
  166. ? ''
  167. : []
  168. // attach listeners
  169. stream.on('aborted', onAborted)
  170. stream.on('close', cleanup)
  171. stream.on('data', onData)
  172. stream.on('end', onEnd)
  173. stream.on('error', onEnd)
  174. // mark sync section complete
  175. sync = false
  176. function done () {
  177. var args = new Array(arguments.length)
  178. // copy arguments
  179. for (var i = 0; i < args.length; i++) {
  180. args[i] = arguments[i]
  181. }
  182. // mark complete
  183. complete = true
  184. if (sync) {
  185. process.nextTick(invokeCallback)
  186. } else {
  187. invokeCallback()
  188. }
  189. function invokeCallback () {
  190. cleanup()
  191. if (args[0]) {
  192. // halt the stream on error
  193. halt(stream)
  194. }
  195. callback.apply(null, args)
  196. }
  197. }
  198. function onAborted () {
  199. if (complete) return
  200. done(createError(400, 'request aborted', {
  201. code: 'ECONNABORTED',
  202. expected: length,
  203. length: length,
  204. received: received,
  205. type: 'request.aborted'
  206. }))
  207. }
  208. function onData (chunk) {
  209. if (complete) return
  210. received += chunk.length
  211. if (limit !== null && received > limit) {
  212. done(createError(413, 'request entity too large', {
  213. limit: limit,
  214. received: received,
  215. type: 'entity.too.large'
  216. }))
  217. } else if (decoder) {
  218. buffer += decoder.write(chunk)
  219. } else {
  220. buffer.push(chunk)
  221. }
  222. }
  223. function onEnd (err) {
  224. if (complete) return
  225. if (err) return done(err)
  226. if (length !== null && received !== length) {
  227. done(createError(400, 'request size did not match content length', {
  228. expected: length,
  229. length: length,
  230. received: received,
  231. type: 'request.size.invalid'
  232. }))
  233. } else {
  234. var string = decoder
  235. ? buffer + (decoder.end() || '')
  236. : Buffer.concat(buffer)
  237. done(null, string)
  238. }
  239. }
  240. function cleanup () {
  241. buffer = null
  242. stream.removeListener('aborted', onAborted)
  243. stream.removeListener('data', onData)
  244. stream.removeListener('end', onEnd)
  245. stream.removeListener('error', onEnd)
  246. stream.removeListener('close', cleanup)
  247. }
  248. }
  249. /**
  250. * Try to require async_hooks
  251. * @private
  252. */
  253. function tryRequireAsyncHooks () {
  254. try {
  255. return require('async_hooks')
  256. } catch (e) {
  257. return {}
  258. }
  259. }
  260. /**
  261. * Wrap function with async resource, if possible.
  262. * AsyncResource.bind static method backported.
  263. * @private
  264. */
  265. function wrap (fn) {
  266. var res
  267. // create anonymous resource
  268. if (asyncHooks.AsyncResource) {
  269. res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')
  270. }
  271. // incompatible node.js
  272. if (!res || !res.runInAsyncScope) {
  273. return fn
  274. }
  275. // return bound function
  276. return res.runInAsyncScope.bind(res, fn, null)
  277. }