source: node_modules/raw-body/index.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 6.7 KB
RevLine 
[d24f17c]1/*!
2 * raw-body
3 * Copyright(c) 2013-2014 Jonathan Ong
4 * Copyright(c) 2014-2022 Douglas Christopher Wilson
5 * MIT Licensed
6 */
7
8'use strict'
9
10/**
11 * Module dependencies.
12 * @private
13 */
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 * @public
24 */
25
26module.exports = getRawBody
27
28/**
29 * Module variables.
30 * @private
31 */
32
33var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: /
34
35/**
36 * Get the decoder for a given encoding.
37 *
38 * @param {string} encoding
39 * @private
40 */
41
42function getDecoder (encoding) {
43 if (!encoding) return null
44
45 try {
46 return iconv.getDecoder(encoding)
47 } catch (e) {
48 // error getting decoder
49 if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e
50
51 // the encoding was not found
52 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} stream
63 * @param {object|string|function} [options]
64 * @param {function} [callback]
65 * @public
66 */
67
68function getRawBody (stream, options, callback) {
69 var done = callback
70 var opts = options || {}
71
72 if (options === true || typeof options === 'string') {
73 // short cut for encoding
74 opts = {
75 encoding: options
76 }
77 }
78
79 if (typeof options === 'function') {
80 done = options
81 opts = {}
82 }
83
84 // validate callback is a function, if provided
85 if (done !== undefined && typeof done !== 'function') {
86 throw new TypeError('argument callback must be a function')
87 }
88
89 // require the callback without promises
90 if (!done && !global.Promise) {
91 throw new TypeError('argument callback is required')
92 }
93
94 // get encoding
95 var encoding = opts.encoding !== true
96 ? opts.encoding
97 : 'utf-8'
98
99 // convert the limit to an integer
100 var limit = bytes.parse(opts.limit)
101
102 // convert the expected length to an integer
103 var length = opts.length != null && !isNaN(opts.length)
104 ? parseInt(opts.length, 10)
105 : null
106
107 if (done) {
108 // classic callback style
109 return readStream(stream, encoding, length, limit, wrap(done))
110 }
111
112 return new Promise(function executor (resolve, reject) {
113 readStream(stream, encoding, length, limit, function onRead (err, buf) {
114 if (err) return reject(err)
115 resolve(buf)
116 })
117 })
118}
119
120/**
121 * Halt a stream.
122 *
123 * @param {Object} stream
124 * @private
125 */
126
127function halt (stream) {
128 // unpipe everything from the stream
129 unpipe(stream)
130
131 // pause stream
132 if (typeof stream.pause === 'function') {
133 stream.pause()
134 }
135}
136
137/**
138 * Read the data from the stream.
139 *
140 * @param {object} stream
141 * @param {string} encoding
142 * @param {number} length
143 * @param {number} limit
144 * @param {function} callback
145 * @public
146 */
147
148function readStream (stream, encoding, length, limit, callback) {
149 var complete = false
150 var sync = true
151
152 // check the length and limit options.
153 // note: we intentionally leave the stream paused,
154 // so users should handle the stream themselves.
155 if (limit !== null && length !== null && length > limit) {
156 return done(createError(413, 'request entity too large', {
157 expected: length,
158 length: length,
159 limit: limit,
160 type: 'entity.too.large'
161 }))
162 }
163
164 // streams1: assert request encoding is buffer.
165 // streams2+: assert the stream encoding is buffer.
166 // stream._decoder: streams1
167 // state.encoding: streams2
168 // state.decoder: streams2, specifically < 0.10.6
169 var state = stream._readableState
170 if (stream._decoder || (state && (state.encoding || state.decoder))) {
171 // developer error
172 return done(createError(500, 'stream encoding should not be set', {
173 type: 'stream.encoding.set'
174 }))
175 }
176
177 if (typeof stream.readable !== 'undefined' && !stream.readable) {
178 return done(createError(500, 'stream is not readable', {
179 type: 'stream.not.readable'
180 }))
181 }
182
183 var received = 0
184 var decoder
185
186 try {
187 decoder = getDecoder(encoding)
188 } catch (err) {
189 return done(err)
190 }
191
192 var buffer = decoder
193 ? ''
194 : []
195
196 // attach listeners
197 stream.on('aborted', onAborted)
198 stream.on('close', cleanup)
199 stream.on('data', onData)
200 stream.on('end', onEnd)
201 stream.on('error', onEnd)
202
203 // mark sync section complete
204 sync = false
205
206 function done () {
207 var args = new Array(arguments.length)
208
209 // copy arguments
210 for (var i = 0; i < args.length; i++) {
211 args[i] = arguments[i]
212 }
213
214 // mark complete
215 complete = true
216
217 if (sync) {
218 process.nextTick(invokeCallback)
219 } else {
220 invokeCallback()
221 }
222
223 function invokeCallback () {
224 cleanup()
225
226 if (args[0]) {
227 // halt the stream on error
228 halt(stream)
229 }
230
231 callback.apply(null, args)
232 }
233 }
234
235 function onAborted () {
236 if (complete) return
237
238 done(createError(400, 'request aborted', {
239 code: 'ECONNABORTED',
240 expected: length,
241 length: length,
242 received: received,
243 type: 'request.aborted'
244 }))
245 }
246
247 function onData (chunk) {
248 if (complete) return
249
250 received += chunk.length
251
252 if (limit !== null && received > limit) {
253 done(createError(413, 'request entity too large', {
254 limit: limit,
255 received: received,
256 type: 'entity.too.large'
257 }))
258 } else if (decoder) {
259 buffer += decoder.write(chunk)
260 } else {
261 buffer.push(chunk)
262 }
263 }
264
265 function onEnd (err) {
266 if (complete) return
267 if (err) return done(err)
268
269 if (length !== null && received !== length) {
270 done(createError(400, 'request size did not match content length', {
271 expected: length,
272 length: length,
273 received: received,
274 type: 'request.size.invalid'
275 }))
276 } else {
277 var string = decoder
278 ? buffer + (decoder.end() || '')
279 : Buffer.concat(buffer)
280 done(null, string)
281 }
282 }
283
284 function cleanup () {
285 buffer = null
286
287 stream.removeListener('aborted', onAborted)
288 stream.removeListener('data', onData)
289 stream.removeListener('end', onEnd)
290 stream.removeListener('error', onEnd)
291 stream.removeListener('close', cleanup)
292 }
293}
294
295/**
296 * Try to require async_hooks
297 * @private
298 */
299
300function tryRequireAsyncHooks () {
301 try {
302 return require('async_hooks')
303 } catch (e) {
304 return {}
305 }
306}
307
308/**
309 * Wrap function with async resource, if possible.
310 * AsyncResource.bind static method backported.
311 * @private
312 */
313
314function wrap (fn) {
315 var res
316
317 // create anonymous resource
318 if (asyncHooks.AsyncResource) {
319 res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')
320 }
321
322 // incompatible node.js
323 if (!res || !res.runInAsyncScope) {
324 return fn
325 }
326
327 // return bound function
328 return res.runInAsyncScope.bind(res, fn, null)
329}
Note: See TracBrowser for help on using the repository browser.