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.
		
		
		
		
			
				
					287 lines
				
				5.9 KiB
			
		
		
			
		
	
	
					287 lines
				
				5.9 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * raw-body
							 | 
						||
| 
								 | 
							
								 * Copyright(c) 2013-2014 Jonathan Ong
							 | 
						||
| 
								 | 
							
								 * Copyright(c) 2014-2015 Douglas Christopher Wilson
							 | 
						||
| 
								 | 
							
								 * MIT Licensed
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Module dependencies.
							 | 
						||
| 
								 | 
							
								 * @private
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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 || {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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, 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'
							 | 
						||
| 
								 | 
							
								    }))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |