| |
| |
| |
| |
| |
| |
| |
| 'use strict' |
|
|
| |
| |
| |
| |
|
|
| var bytes = require('bytes') |
| var createError = require('http-errors') |
| var iconv = require('iconv-lite') |
| var unpipe = require('unpipe') |
|
|
| |
| |
| |
| |
|
|
| module.exports = getRawBody |
|
|
| |
| |
| |
| |
|
|
| var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: / |
|
|
| |
| |
| |
| |
| |
| |
|
|
| function getDecoder (encoding) { |
| if (!encoding) return null |
|
|
| try { |
| return iconv.getDecoder(encoding) |
| } catch (e) { |
| |
| if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e |
|
|
| |
| throw createError(415, 'specified encoding unsupported', { |
| encoding: encoding, |
| type: 'encoding.unsupported' |
| }) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function getRawBody (stream, options, callback) { |
| var done = callback |
| var opts = options || {} |
|
|
| if (options === true || typeof options === 'string') { |
| |
| opts = { |
| encoding: options |
| } |
| } |
|
|
| if (typeof options === 'function') { |
| done = options |
| opts = {} |
| } |
|
|
| |
| if (done !== undefined && typeof done !== 'function') { |
| throw new TypeError('argument callback must be a function') |
| } |
|
|
| |
| if (!done && !global.Promise) { |
| throw new TypeError('argument callback is required') |
| } |
|
|
| |
| var encoding = opts.encoding !== true |
| ? opts.encoding |
| : 'utf-8' |
|
|
| |
| var limit = bytes.parse(opts.limit) |
|
|
| |
| var length = opts.length != null && !isNaN(opts.length) |
| ? parseInt(opts.length, 10) |
| : null |
|
|
| if (done) { |
| |
| 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) |
| }) |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| function halt (stream) { |
| |
| unpipe(stream) |
|
|
| |
| if (typeof stream.pause === 'function') { |
| stream.pause() |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function readStream (stream, encoding, length, limit, callback) { |
| var complete = false |
| var sync = true |
|
|
| |
| |
| |
| 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' |
| })) |
| } |
|
|
| |
| |
| |
| |
| |
| var state = stream._readableState |
| if (stream._decoder || (state && (state.encoding || state.decoder))) { |
| |
| 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 |
| ? '' |
| : [] |
|
|
| |
| stream.on('aborted', onAborted) |
| stream.on('close', cleanup) |
| stream.on('data', onData) |
| stream.on('end', onEnd) |
| stream.on('error', onEnd) |
|
|
| |
| sync = false |
|
|
| function done () { |
| var args = new Array(arguments.length) |
|
|
| |
| for (var i = 0; i < args.length; i++) { |
| args[i] = arguments[i] |
| } |
|
|
| |
| complete = true |
|
|
| if (sync) { |
| process.nextTick(invokeCallback) |
| } else { |
| invokeCallback() |
| } |
|
|
| function invokeCallback () { |
| cleanup() |
|
|
| if (args[0]) { |
| |
| 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) |
| } |
| } |
|
|