aboutsummaryrefslogtreecommitdiffhomepage
path: root/node_modules/raw-body/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/raw-body/index.js')
-rw-r--r--node_modules/raw-body/index.js286
1 files changed, 286 insertions, 0 deletions
diff --git a/node_modules/raw-body/index.js b/node_modules/raw-body/index.js
new file mode 100644
index 0000000..7fe8186
--- /dev/null
+++ b/node_modules/raw-body/index.js
@@ -0,0 +1,286 @@
+/*!
+ * 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)
+ }
+}