| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 | /*! * raw-body * Copyright(c) 2013-2014 Jonathan Ong * Copyright(c) 2014-2022 Douglas Christopher Wilson * MIT Licensed */'use strict'/** * Module dependencies. * @private */var asyncHooks = tryRequireAsyncHooks()var bytes = require('bytes')var createError = require('http-errors')var iconv = require('iconv-lite')var unpipe = require('unpipe')/** * Module exports. * @public */module.exports = getRawBody/** * Module variables. * @private */var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: //** * Get the decoder for a given encoding. * * @param {string} encoding * @private */function getDecoder (encoding) {  if (!encoding) return null  try {    return iconv.getDecoder(encoding)  } catch (e) {    // error getting decoder    if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e    // the encoding was not found    throw createError(415, 'specified encoding unsupported', {      encoding: encoding,      type: 'encoding.unsupported'    })  }}/** * Get the raw body of a stream (typically HTTP). * * @param {object} stream * @param {object|string|function} [options] * @param {function} [callback] * @public */function getRawBody (stream, options, callback) {  var done = callback  var opts = options || {}  // light validation  if (stream === undefined) {    throw new TypeError('argument stream is required')  } else if (typeof stream !== 'object' || stream === null || typeof stream.on !== 'function') {    throw new TypeError('argument stream must be a stream')  }  if (options === true || typeof options === 'string') {    // short cut for encoding    opts = {      encoding: options    }  }  if (typeof options === 'function') {    done = options    opts = {}  }  // validate callback is a function, if provided  if (done !== undefined && typeof done !== 'function') {    throw new TypeError('argument callback must be a function')  }  // require the callback without promises  if (!done && !global.Promise) {    throw new TypeError('argument callback is required')  }  // get encoding  var encoding = opts.encoding !== true    ? opts.encoding    : 'utf-8'  // convert the limit to an integer  var limit = bytes.parse(opts.limit)  // convert the expected length to an integer  var length = opts.length != null && !isNaN(opts.length)    ? parseInt(opts.length, 10)    : null  if (done) {    // classic callback style    return readStream(stream, encoding, length, limit, wrap(done))  }  return new Promise(function executor (resolve, reject) {    readStream(stream, encoding, length, limit, function onRead (err, buf) {      if (err) return reject(err)      resolve(buf)    })  })}/** * Halt a stream. * * @param {Object} stream * @private */function halt (stream) {  // unpipe everything from the stream  unpipe(stream)  // pause stream  if (typeof stream.pause === 'function') {    stream.pause()  }}/** * Read the data from the stream. * * @param {object} stream * @param {string} encoding * @param {number} length * @param {number} limit * @param {function} callback * @public */function readStream (stream, encoding, length, limit, callback) {  var complete = false  var sync = true  // check the length and limit options.  // note: we intentionally leave the stream paused,  // so users should handle the stream themselves.  if (limit !== null && length !== null && length > limit) {    return done(createError(413, 'request entity too large', {      expected: length,      length: length,      limit: limit,      type: 'entity.too.large'    }))  }  // streams1: assert request encoding is buffer.  // streams2+: assert the stream encoding is buffer.  //   stream._decoder: streams1  //   state.encoding: streams2  //   state.decoder: streams2, specifically < 0.10.6  var state = stream._readableState  if (stream._decoder || (state && (state.encoding || state.decoder))) {    // developer error    return done(createError(500, 'stream encoding should not be set', {      type: 'stream.encoding.set'    }))  }  if (typeof stream.readable !== 'undefined' && !stream.readable) {    return done(createError(500, 'stream is not readable', {      type: 'stream.not.readable'    }))  }  var received = 0  var decoder  try {    decoder = getDecoder(encoding)  } catch (err) {    return done(err)  }  var buffer = decoder    ? ''    : []  // attach listeners  stream.on('aborted', onAborted)  stream.on('close', cleanup)  stream.on('data', onData)  stream.on('end', onEnd)  stream.on('error', onEnd)  // mark sync section complete  sync = false  function done () {    var args = new Array(arguments.length)    // copy arguments    for (var i = 0; i < args.length; i++) {      args[i] = arguments[i]    }    // mark complete    complete = true    if (sync) {      process.nextTick(invokeCallback)    } else {      invokeCallback()    }    function invokeCallback () {      cleanup()      if (args[0]) {        // halt the stream on error        halt(stream)      }      callback.apply(null, args)    }  }  function onAborted () {    if (complete) return    done(createError(400, 'request aborted', {      code: 'ECONNABORTED',      expected: length,      length: length,      received: received,      type: 'request.aborted'    }))  }  function onData (chunk) {    if (complete) return    received += chunk.length    if (limit !== null && received > limit) {      done(createError(413, 'request entity too large', {        limit: limit,        received: received,        type: 'entity.too.large'      }))    } else if (decoder) {      buffer += decoder.write(chunk)    } else {      buffer.push(chunk)    }  }  function onEnd (err) {    if (complete) return    if (err) return done(err)    if (length !== null && received !== length) {      done(createError(400, 'request size did not match content length', {        expected: length,        length: length,        received: received,        type: 'request.size.invalid'      }))    } else {      var string = decoder        ? buffer + (decoder.end() || '')        : Buffer.concat(buffer)      done(null, string)    }  }  function cleanup () {    buffer = null    stream.removeListener('aborted', onAborted)    stream.removeListener('data', onData)    stream.removeListener('end', onEnd)    stream.removeListener('error', onEnd)    stream.removeListener('close', cleanup)  }}/** * Try to require async_hooks * @private */function tryRequireAsyncHooks () {  try {    return require('async_hooks')  } catch (e) {    return {}  }}/** * Wrap function with async resource, if possible. * AsyncResource.bind static method backported. * @private */function wrap (fn) {  var res  // create anonymous resource  if (asyncHooks.AsyncResource) {    res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')  }  // incompatible node.js  if (!res || !res.runInAsyncScope) {    return fn  }  // return bound function  return res.runInAsyncScope.bind(res, fn, null)}
 |