| |
| |
| |
| |
|
|
|
|
| 'use strict'
|
|
|
| |
| |
| |
|
|
|
|
| const createError = require('http-errors')
|
| const getBody = require('raw-body')
|
| const iconv = require('iconv-lite')
|
| const onFinished = require('on-finished')
|
| const zlib = require('node:zlib')
|
| const hasBody = require('type-is').hasBody
|
| const { getCharset } = require('./utils')
|
|
|
| |
| |
|
|
|
|
| module.exports = read
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function read (req, res, next, parse, debug, options) {
|
| if (onFinished.isFinished(req)) {
|
| debug('body already parsed')
|
| next()
|
| return
|
| }
|
|
|
| if (!('body' in req)) {
|
| req.body = undefined
|
| }
|
|
|
|
|
| if (!hasBody(req)) {
|
| debug('skip empty body')
|
| next()
|
| return
|
| }
|
|
|
| debug('content-type %j', req.headers['content-type'])
|
|
|
|
|
| if (!options.shouldParse(req)) {
|
| debug('skip parsing')
|
| next()
|
| return
|
| }
|
|
|
| let encoding = null
|
| if (options?.skipCharset !== true) {
|
| encoding = getCharset(req) || options.defaultCharset
|
|
|
|
|
| if (!!options?.isValidCharset && !options.isValidCharset(encoding)) {
|
| debug('invalid charset')
|
| next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
| charset: encoding,
|
| type: 'charset.unsupported'
|
| }))
|
| return
|
| }
|
| }
|
|
|
| let length
|
| const opts = options
|
| let stream
|
|
|
|
|
| const verify = opts.verify
|
|
|
| try {
|
|
|
| stream = contentstream(req, debug, opts.inflate)
|
| length = stream.length
|
| stream.length = undefined
|
| } catch (err) {
|
| return next(err)
|
| }
|
|
|
|
|
| opts.length = length
|
| opts.encoding = verify
|
| ? null
|
| : encoding
|
|
|
|
|
| if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
|
| return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
| charset: encoding.toLowerCase(),
|
| type: 'charset.unsupported'
|
| }))
|
| }
|
|
|
|
|
| debug('read body')
|
| getBody(stream, opts, function (error, body) {
|
| if (error) {
|
| let _error
|
|
|
| if (error.type === 'encoding.unsupported') {
|
|
|
| _error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
| charset: encoding.toLowerCase(),
|
| type: 'charset.unsupported'
|
| })
|
| } else {
|
|
|
| _error = createError(400, error)
|
| }
|
|
|
|
|
| if (stream !== req) {
|
| req.unpipe()
|
| stream.destroy()
|
| }
|
|
|
|
|
| dump(req, function onfinished () {
|
| next(createError(400, _error))
|
| })
|
| return
|
| }
|
|
|
|
|
| if (verify) {
|
| try {
|
| debug('verify body')
|
| verify(req, res, body, encoding)
|
| } catch (err) {
|
| next(createError(403, err, {
|
| body: body,
|
| type: err.type || 'entity.verify.failed'
|
| }))
|
| return
|
| }
|
| }
|
|
|
|
|
| let str = body
|
| try {
|
| debug('parse body')
|
| str = typeof body !== 'string' && encoding !== null
|
| ? iconv.decode(body, encoding)
|
| : body
|
| req.body = parse(str, encoding)
|
| } catch (err) {
|
| next(createError(400, err, {
|
| body: str,
|
| type: err.type || 'entity.parse.failed'
|
| }))
|
| return
|
| }
|
|
|
| next()
|
| })
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function contentstream (req, debug, inflate) {
|
| const encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
|
| const length = req.headers['content-length']
|
|
|
| debug('content-encoding "%s"', encoding)
|
|
|
| if (inflate === false && encoding !== 'identity') {
|
| throw createError(415, 'content encoding unsupported', {
|
| encoding: encoding,
|
| type: 'encoding.unsupported'
|
| })
|
| }
|
|
|
| if (encoding === 'identity') {
|
| req.length = length
|
| return req
|
| }
|
|
|
| const stream = createDecompressionStream(encoding, debug)
|
| req.pipe(stream)
|
| return stream
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| function createDecompressionStream (encoding, debug) {
|
| switch (encoding) {
|
| case 'deflate':
|
| debug('inflate body')
|
| return zlib.createInflate()
|
| case 'gzip':
|
| debug('gunzip body')
|
| return zlib.createGunzip()
|
| case 'br':
|
| debug('brotli decompress body')
|
| return zlib.createBrotliDecompress()
|
| default:
|
| throw createError(415, 'unsupported content encoding "' + encoding + '"', {
|
| encoding: encoding,
|
| type: 'encoding.unsupported'
|
| })
|
| }
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| function dump (req, callback) {
|
| if (onFinished.isFinished(req)) {
|
| callback(null)
|
| } else {
|
| onFinished(req, callback)
|
| req.resume()
|
| }
|
| }
|
|
|