var bytes = require('bytes') // NOTE: the trailing slash is not a typo var StringDecoder = require('string_decoder/').StringDecoder module.exports = function (stream, options, done) { if (typeof options === 'function') { done = options options = {} } else if (!options) { options = {} } else if (options === true) { options = { encoding: 'utf8' } } // convert the limit to an integer var limit = null if (typeof options.limit === 'number') limit = options.limit if (typeof options.limit === 'string') limit = bytes(options.limit) // convert the expected length to an integer var length = null if (options.length != null && !isNaN(options.length)) length = parseInt(options.length, 10) // 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) { if (typeof stream.pause === 'function') stream.pause() process.nextTick(function () { var err = makeError('request entity too large', 'entity.too.large') err.status = err.statusCode = 413 err.length = err.expected = length err.limit = limit done(err) }) return defer } // 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))) { if (typeof stream.pause === 'function') stream.pause() process.nextTick(function () { var err = makeError('stream encoding should not be set', 'stream.encoding.set') // developer error err.status = err.statusCode = 500 done(err) }) return defer } var received = 0 // note: we delegate any invalid encodings to the constructor var decoder = options.encoding ? new StringDecoder(options.encoding === true ? 'utf8' : options.encoding) : null var buffer = decoder ? '' : [] stream.on('data', onData) stream.once('end', onEnd) stream.once('error', onEnd) stream.once('close', cleanup) return defer // yieldable support function defer(fn) { done = fn } function onData(chunk) { received += chunk.length decoder ? buffer += decoder.write(chunk) : buffer.push(chunk) if (limit !== null && received > limit) { if (typeof stream.pause === 'function') stream.pause() var err = makeError('request entity too large', 'entity.too.large') err.status = err.statusCode = 413 err.received = received err.limit = limit done(err) cleanup() } } function onEnd(err) { if (err) { if (typeof stream.pause === 'function') stream.pause() done(err) } else if (length !== null && received !== length) { err = makeError('request size did not match content length', 'request.size.invalid') err.status = err.statusCode = 400 err.received = received err.length = err.expected = length done(err) } else { done(null, decoder ? buffer + decoder.end() : Buffer.concat(buffer) ) } cleanup() } function cleanup() { received = buffer = null stream.removeListener('data', onData) stream.removeListener('end', onEnd) stream.removeListener('error', onEnd) stream.removeListener('close', cleanup) } } // to create serializable errors you must re-set message so // that it is enumerable and you must re configure the type // property so that is writable and enumerable function makeError(message, type) { var error = new Error() error.message = message Object.defineProperty(error, 'type', { value: type, enumerable: true, writable: true, configurable: true }) return error }