3 * Copyright(c) 2013-2014 Jonathan Ong
4 * Copyright(c) 2014-2015 Douglas Christopher Wilson
11 * Module dependencies.
15 var bytes = require('bytes')
16 var iconv = require('iconv-lite')
17 var unpipe = require('unpipe')
24 module.exports = getRawBody
31 var iconvEncodingMessageRegExp = /^Encoding not recognized: /
34 * Get the decoder for a given encoding.
36 * @param {string} encoding
40 function getDecoder (encoding) {
41 if (!encoding) return null
44 return iconv.getDecoder(encoding)
46 // error getting decoder
47 if (!iconvEncodingMessageRegExp.test(e.message)) throw e
49 // the encoding was not found
50 throw createError(415, 'specified encoding unsupported', 'encoding.unsupported', {
57 * Get the raw body of a stream (typically HTTP).
59 * @param {object} stream
60 * @param {object|string|function} [options]
61 * @param {function} [callback]
65 function getRawBody (stream, options, callback) {
67 var opts = options || {}
69 if (options === true || typeof options === 'string') {
70 // short cut for encoding
76 if (typeof options === 'function') {
81 // validate callback is a function, if provided
82 if (done !== undefined && typeof done !== 'function') {
83 throw new TypeError('argument callback must be a function')
86 // require the callback without promises
87 if (!done && !global.Promise) {
88 throw new TypeError('argument callback is required')
92 var encoding = opts.encoding !== true
96 // convert the limit to an integer
97 var limit = bytes.parse(opts.limit)
99 // convert the expected length to an integer
100 var length = opts.length != null && !isNaN(opts.length)
101 ? parseInt(opts.length, 10)
105 // classic callback style
106 return readStream(stream, encoding, length, limit, done)
109 return new Promise(function executor (resolve, reject) {
110 readStream(stream, encoding, length, limit, function onRead (err, buf) {
111 if (err) return reject(err)
120 * @param {Object} stream
124 function halt (stream) {
125 // unpipe everything from the stream
129 if (typeof stream.pause === 'function') {
135 * Make a serializable error object.
137 * To create serializable errors you must re-set message so
138 * that it is enumerable and you must re configure the type
139 * property so that is writable and enumerable.
141 * @param {number} status
142 * @param {string} message
143 * @param {string} type
144 * @param {object} props
148 function createError (status, message, type, props) {
149 var error = new Error()
151 // capture stack trace
152 Error.captureStackTrace(error, createError)
154 // set free-form properties
155 for (var prop in props) {
156 error[prop] = props[prop]
160 error.message = message
163 error.status = status
164 error.statusCode = status
167 Object.defineProperty(error, 'type', {
178 * Read the data from the stream.
180 * @param {object} stream
181 * @param {string} encoding
182 * @param {number} length
183 * @param {number} limit
184 * @param {function} callback
188 function readStream (stream, encoding, length, limit, callback) {
192 // check the length and limit options.
193 // note: we intentionally leave the stream paused,
194 // so users should handle the stream themselves.
195 if (limit !== null && length !== null && length > limit) {
196 return done(createError(413, 'request entity too large', 'entity.too.large', {
203 // streams1: assert request encoding is buffer.
204 // streams2+: assert the stream encoding is buffer.
205 // stream._decoder: streams1
206 // state.encoding: streams2
207 // state.decoder: streams2, specifically < 0.10.6
208 var state = stream._readableState
209 if (stream._decoder || (state && (state.encoding || state.decoder))) {
211 return done(createError(500, 'stream encoding should not be set', 'stream.encoding.set'))
218 decoder = getDecoder(encoding)
228 stream.on('aborted', onAborted)
229 stream.on('close', cleanup)
230 stream.on('data', onData)
231 stream.on('end', onEnd)
232 stream.on('error', onEnd)
234 // mark sync section complete
238 var args = new Array(arguments.length)
241 for (var i = 0; i < args.length; i++) {
242 args[i] = arguments[i]
249 process.nextTick(invokeCallback)
254 function invokeCallback () {
258 // halt the stream on error
262 callback.apply(null, args)
266 function onAborted () {
269 done(createError(400, 'request aborted', 'request.aborted', {
270 code: 'ECONNABORTED',
277 function onData (chunk) {
280 received += chunk.length
282 ? buffer += decoder.write(chunk)
285 if (limit !== null && received > limit) {
286 done(createError(413, 'request entity too large', 'entity.too.large', {
293 function onEnd (err) {
295 if (err) return done(err)
297 if (length !== null && received !== length) {
298 done(createError(400, 'request size did not match content length', 'request.size.invalid', {
305 ? buffer + (decoder.end() || '')
306 : Buffer.concat(buffer)
311 function cleanup () {
314 stream.removeListener('aborted', onAborted)
315 stream.removeListener('data', onData)
316 stream.removeListener('end', onEnd)
317 stream.removeListener('error', onEnd)
318 stream.removeListener('close', cleanup)