AK-21/Graphite-Industrial-Intelligence
0
1/*!2 * raw-body3 * Copyright(c) 2013-2014 Jonathan Ong4 * Copyright(c) 2014-2022 Douglas Christopher Wilson5 * MIT Licensed6 */7 8'use strict'9 10/**11 * Module dependencies.12 * @private13 */14 15var asyncHooks = tryRequireAsyncHooks()16var bytes = require('bytes')17var createError = require('http-errors')18var iconv = require('iconv-lite')19var unpipe = require('unpipe')20 21/**22 * Module exports.23 * @public24 */25 26module.exports = getRawBody27 28/**29 * Module variables.30 * @private31 */32 33var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: /34 35/**36 * Get the decoder for a given encoding.37 *38 * @param {string} encoding39 * @private40 */41 42function getDecoder (encoding) {43 if (!encoding) return null44 45 try {46 return iconv.getDecoder(encoding)47 } catch (e) {48 // error getting decoder49 if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e50 51 // the encoding was not found52 throw createError(415, 'specified encoding unsupported', {53 encoding: encoding,54 type: 'encoding.unsupported'55 })56 }57}58 59/**60 * Get the raw body of a stream (typically HTTP).61 *62 * @param {object} stream63 * @param {object|string|function} [options]64 * @param {function} [callback]65 * @public66 */67 68function getRawBody (stream, options, callback) {69 var done = callback70 var opts = options || {}71 72 // light validation73 if (stream === undefined) {74 throw new TypeError('argument stream is required')75 } else if (typeof stream !== 'object' || stream === null || typeof stream.on !== 'function') {76 throw new TypeError('argument stream must be a stream')77 }78 79 if (options === true || typeof options === 'string') {80 // short cut for encoding81 opts = {82 encoding: options83 }84 }85 86 if (typeof options === 'function') {87 done = options88 opts = {}89 }90 91 // validate callback is a function, if provided92 if (done !== undefined && typeof done !== 'function') {93 throw new TypeError('argument callback must be a function')94 }95 96 // require the callback without promises97 if (!done && !global.Promise) {98 throw new TypeError('argument callback is required')99 }100 101 // get encoding102 var encoding = opts.encoding !== true103 ? opts.encoding104 : 'utf-8'105 106 // convert the limit to an integer107 var limit = bytes.parse(opts.limit)108 109 // convert the expected length to an integer110 var length = opts.length != null && !isNaN(opts.length)111 ? parseInt(opts.length, 10)112 : null113 114 if (done) {115 // classic callback style116 return readStream(stream, encoding, length, limit, wrap(done))117 }118 119 return new Promise(function executor (resolve, reject) {120 readStream(stream, encoding, length, limit, function onRead (err, buf) {121 if (err) return reject(err)122 resolve(buf)123 })124 })125}126 127/**128 * Halt a stream.129 *130 * @param {Object} stream131 * @private132 */133 134function halt (stream) {135 // unpipe everything from the stream136 unpipe(stream)137 138 // pause stream139 if (typeof stream.pause === 'function') {140 stream.pause()141 }142}143 144/**145 * Read the data from the stream.146 *147 * @param {object} stream148 * @param {string} encoding149 * @param {number} length150 * @param {number} limit151 * @param {function} callback152 * @public153 */154 155function readStream (stream, encoding, length, limit, callback) {156 var complete = false157 var sync = true158 159 // check the length and limit options.160 // note: we intentionally leave the stream paused,161 // so users should handle the stream themselves.162 if (limit !== null && length !== null && length > limit) {163 return done(createError(413, 'request entity too large', {164 expected: length,165 length: length,166 limit: limit,167 type: 'entity.too.large'168 }))169 }170 171 // streams1: assert request encoding is buffer.172 // streams2+: assert the stream encoding is buffer.173 // stream._decoder: streams1174 // state.encoding: streams2175 // state.decoder: streams2, specifically < 0.10.6176 var state = stream._readableState177 if (stream._decoder || (state && (state.encoding || state.decoder))) {178 // developer error179 return done(createError(500, 'stream encoding should not be set', {180 type: 'stream.encoding.set'181 }))182 }183 184 if (typeof stream.readable !== 'undefined' && !stream.readable) {185 return done(createError(500, 'stream is not readable', {186 type: 'stream.not.readable'187 }))188 }189 190 var received = 0191 var decoder192 193 try {194 decoder = getDecoder(encoding)195 } catch (err) {196 return done(err)197 }198 199 var buffer = decoder200 ? ''201 : []202 203 // attach listeners204 stream.on('aborted', onAborted)205 stream.on('close', cleanup)206 stream.on('data', onData)207 stream.on('end', onEnd)208 stream.on('error', onEnd)209 210 // mark sync section complete211 sync = false212 213 function done () {214 var args = new Array(arguments.length)215 216 // copy arguments217 for (var i = 0; i < args.length; i++) {218 args[i] = arguments[i]219 }220 221 // mark complete222 complete = true223 224 if (sync) {225 process.nextTick(invokeCallback)226 } else {227 invokeCallback()228 }229 230 function invokeCallback () {231 cleanup()232 233 if (args[0]) {234 // halt the stream on error235 halt(stream)236 }237 238 callback.apply(null, args)239 }240 }241 242 function onAborted () {243 if (complete) return244 245 done(createError(400, 'request aborted', {246 code: 'ECONNABORTED',247 expected: length,248 length: length,249 received: received,250 type: 'request.aborted'251 }))252 }253 254 function onData (chunk) {255 if (complete) return256 257 received += chunk.length258 259 if (limit !== null && received > limit) {260 done(createError(413, 'request entity too large', {261 limit: limit,262 received: received,263 type: 'entity.too.large'264 }))265 } else if (decoder) {266 buffer += decoder.write(chunk)267 } else {268 buffer.push(chunk)269 }270 }271 272 function onEnd (err) {273 if (complete) return274 if (err) return done(err)275 276 if (length !== null && received !== length) {277 done(createError(400, 'request size did not match content length', {278 expected: length,279 length: length,280 received: received,281 type: 'request.size.invalid'282 }))283 } else {284 var string = decoder285 ? buffer + (decoder.end() || '')286 : Buffer.concat(buffer)287 done(null, string)288 }289 }290 291 function cleanup () {292 buffer = null293 294 stream.removeListener('aborted', onAborted)295 stream.removeListener('data', onData)296 stream.removeListener('end', onEnd)297 stream.removeListener('error', onEnd)298 stream.removeListener('close', cleanup)299 }300}301 302/**303 * Try to require async_hooks304 * @private305 */306 307function tryRequireAsyncHooks () {308 try {309 return require('async_hooks')310 } catch (e) {311 return {}312 }313}314 315/**316 * Wrap function with async resource, if possible.317 * AsyncResource.bind static method backported.318 * @private319 */320 321function wrap (fn) {322 var res323 324 // create anonymous resource325 if (asyncHooks.AsyncResource) {326 res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')327 }328 329 // incompatible node.js330 if (!res || !res.runInAsyncScope) {331 return fn332 }333 334 // return bound function335 return res.runInAsyncScope.bind(res, fn, null)336}337 