Backend half

This commit is contained in:
2025-07-11 19:56:28 +02:00
parent fa868e7c1d
commit 8600fa7c1d
19426 changed files with 3750448 additions and 8108 deletions
+22 -27
View File
@@ -12,10 +12,12 @@
*/
var createError = require('http-errors')
var destroy = require('destroy')
var getBody = require('raw-body')
var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var zlib = require('node:zlib')
var unpipe = require('unpipe')
var zlib = require('zlib')
/**
* Module exports.
@@ -40,6 +42,9 @@ function read (req, res, next, parse, debug, options) {
var opts = options
var stream
// flag as parsed
req._body = true
// read options
var encoding = opts.encoding !== null
? opts.encoding
@@ -88,8 +93,8 @@ function read (req, res, next, parse, debug, options) {
// unpipe from stream and destroy
if (stream !== req) {
req.unpipe()
stream.destroy()
unpipe(req)
destroy(stream, true)
}
// read off entire request
@@ -120,7 +125,7 @@ function read (req, res, next, parse, debug, options) {
str = typeof body !== 'string' && encoding !== null
? iconv.decode(body, encoding)
: body
req.body = parse(str, encoding)
req.body = parse(str)
} catch (err) {
next(createError(400, err, {
body: str,
@@ -146,6 +151,7 @@ function read (req, res, next, parse, debug, options) {
function contentstream (req, debug, inflate) {
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
var length = req.headers['content-length']
var stream
debug('content-encoding "%s"', encoding)
@@ -156,40 +162,29 @@ function contentstream (req, debug, inflate) {
})
}
if (encoding === 'identity') {
req.length = length
return req
}
var stream = createDecompressionStream(encoding, debug)
req.pipe(stream)
return stream
}
/**
* Create a decompression stream for the given encoding.
* @param {string} encoding
* @param {function} debug
* @return {object}
* @api private
*/
function createDecompressionStream (encoding, debug) {
switch (encoding) {
case 'deflate':
stream = zlib.createInflate()
debug('inflate body')
return zlib.createInflate()
req.pipe(stream)
break
case 'gzip':
stream = zlib.createGunzip()
debug('gunzip body')
return zlib.createGunzip()
case 'br':
debug('brotli decompress body')
return zlib.createBrotliDecompress()
req.pipe(stream)
break
case 'identity':
stream = req
stream.length = length
break
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
return stream
}
/**
+53 -12
View File
@@ -12,12 +12,12 @@
* @private
*/
var bytes = require('bytes')
var contentType = require('content-type')
var createError = require('http-errors')
var debug = require('debug')('body-parser:json')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { getCharset, normalizeOptions } = require('../utils')
/**
* Module exports.
@@ -51,10 +51,25 @@ var JSON_SYNTAX_REGEXP = /#+/g
*/
function json (options) {
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/json')
var opts = options || {}
var reviver = options?.reviver
var strict = options?.strict !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var inflate = opts.inflate !== false
var reviver = opts.reviver
var strict = opts.strict !== false
var type = opts.type || 'application/json'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
if (body.length === 0) {
@@ -84,15 +99,13 @@ function json (options) {
}
return function jsonParser (req, res, next) {
if (isFinished(req)) {
if (req._body) {
debug('body already parsed')
next()
return
}
if (!('body' in req)) {
req.body = undefined
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
@@ -124,9 +137,9 @@ function json (options) {
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate,
limit,
verify
inflate: inflate,
limit: limit,
verify: verify
})
}
}
@@ -180,6 +193,21 @@ function firstchar (str) {
: undefined
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Normalize a SyntaxError for JSON.parse.
*
@@ -204,3 +232,16 @@ function normalizeJsonSyntaxError (error, obj) {
return error
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
+36 -10
View File
@@ -10,11 +10,10 @@
* Module dependencies.
*/
var bytes = require('bytes')
var debug = require('debug')('body-parser:raw')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { normalizeOptions } = require('../utils')
/**
* Module exports.
@@ -31,22 +30,36 @@ module.exports = raw
*/
function raw (options) {
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/octet-stream')
var opts = options || {}
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'application/octet-stream'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (buf) {
return buf
}
return function rawParser (req, res, next) {
if (isFinished(req)) {
if (req._body) {
debug('body already parsed')
next()
return
}
if (!('body' in req)) {
req.body = undefined
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
@@ -67,9 +80,22 @@ function raw (options) {
// read
read(req, res, next, parse, debug, {
encoding: null,
inflate,
limit,
verify
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
+52 -11
View File
@@ -10,11 +10,11 @@
* Module dependencies.
*/
var bytes = require('bytes')
var contentType = require('content-type')
var debug = require('debug')('body-parser:text')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { getCharset, normalizeOptions } = require('../utils')
/**
* Module exports.
@@ -31,24 +31,37 @@ module.exports = text
*/
function text (options) {
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'text/plain')
var opts = options || {}
var defaultCharset = options?.defaultCharset || 'utf-8'
var defaultCharset = opts.defaultCharset || 'utf-8'
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'text/plain'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (buf) {
return buf
}
return function textParser (req, res, next) {
if (isFinished(req)) {
if (req._body) {
debug('body already parsed')
next()
return
}
if (!('body' in req)) {
req.body = undefined
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
@@ -72,9 +85,37 @@ function text (options) {
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate,
limit,
verify
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
+167 -37
View File
@@ -12,13 +12,13 @@
* @private
*/
var bytes = require('bytes')
var contentType = require('content-type')
var createError = require('http-errors')
var debug = require('debug')('body-parser:urlencoded')
var isFinished = require('on-finished').isFinished
var deprecate = require('depd')('body-parser')
var read = require('../read')
var typeis = require('type-is')
var qs = require('qs')
var { getCharset, normalizeOptions } = require('../utils')
/**
* Module exports.
@@ -26,6 +26,12 @@ var { getCharset, normalizeOptions } = require('../utils')
module.exports = urlencoded
/**
* Cache of parser modules.
*/
var parsers = Object.create(null)
/**
* Create a middleware to parse urlencoded bodies.
*
@@ -35,32 +41,52 @@ module.exports = urlencoded
*/
function urlencoded (options) {
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/x-www-form-urlencoded')
var opts = options || {}
var defaultCharset = options?.defaultCharset || 'utf-8'
if (defaultCharset !== 'utf-8' && defaultCharset !== 'iso-8859-1') {
throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1')
// notice because option default will flip in next major
if (opts.extended === undefined) {
deprecate('undefined extended: provide extended option')
}
var extended = opts.extended !== false
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'application/x-www-form-urlencoded'
var verify = opts.verify || false
var depth = typeof opts.depth !== 'number'
? Number(opts.depth || 32)
: opts.depth
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate query parser
var queryparse = createQueryParser(options)
var queryparse = extended
? extendedparser(opts)
: simpleparser(opts)
function parse (body, encoding) {
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
return body.length
? queryparse(body, encoding)
? queryparse(body)
: {}
}
return function urlencodedParser (req, res, next) {
if (isFinished(req)) {
if (req._body) {
debug('body already parsed')
next()
return
}
if (!('body' in req)) {
req.body = undefined
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
@@ -79,8 +105,8 @@ function urlencoded (options) {
}
// assert charset
var charset = getCharset(req) || defaultCharset
if (charset !== 'utf-8' && charset !== 'iso-8859-1') {
var charset = getCharset(req) || 'utf-8'
if (charset !== 'utf-8') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
@@ -91,10 +117,12 @@ function urlencoded (options) {
// read
read(req, res, next, parse, debug, {
debug: debug,
encoding: charset,
inflate,
limit,
verify
inflate: inflate,
limit: limit,
verify: verify,
depth: depth
})
}
}
@@ -105,14 +133,15 @@ function urlencoded (options) {
* @param {object} options
*/
function createQueryParser (options) {
var extended = Boolean(options?.extended)
var parameterLimit = options?.parameterLimit !== undefined
? options?.parameterLimit
function extendedparser (options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
var charsetSentinel = options?.charsetSentinel
var interpretNumericEntities = options?.interpretNumericEntities
var depth = extended ? (options?.depth !== undefined ? options?.depth : 32) : 0
var depth = typeof options.depth !== 'number'
? Number(options.depth || 32)
: options.depth
var parse = parser('qs')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
@@ -126,7 +155,7 @@ function createQueryParser (options) {
parameterLimit = parameterLimit | 0
}
return function queryparse (body, encoding) {
return function queryparse (body) {
var paramCount = parameterCount(body, parameterLimit)
if (paramCount === undefined) {
@@ -136,19 +165,16 @@ function createQueryParser (options) {
})
}
var arrayLimit = extended ? Math.max(100, paramCount) : 0
var arrayLimit = Math.max(100, paramCount)
debug('parse ' + (extended ? 'extended ' : '') + 'urlencoding')
debug('parse extended urlencoding')
try {
return qs.parse(body, {
return parse(body, {
allowPrototypes: true,
arrayLimit: arrayLimit,
depth: depth,
charsetSentinel: charsetSentinel,
interpretNumericEntities: interpretNumericEntities,
charset: encoding,
parameterLimit: parameterLimit,
strictDepth: true
strictDepth: true,
parameterLimit: parameterLimit
})
} catch (err) {
if (err instanceof RangeError) {
@@ -162,6 +188,21 @@ function createQueryParser (options) {
}
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Count the number of parameters, stopping once limit reached
*
@@ -171,7 +212,96 @@ function createQueryParser (options) {
*/
function parameterCount (body, limit) {
var len = body.split('&').length
var count = 0
var index = 0
return len > limit ? undefined : len - 1
while ((index = body.indexOf('&', index)) !== -1) {
count++
index++
if (count === limit) {
return undefined
}
}
return count
}
/**
* Get parser for module name dynamically.
*
* @param {string} name
* @return {function}
* @api private
*/
function parser (name) {
var mod = parsers[name]
if (mod !== undefined) {
return mod.parse
}
// this uses a switch for static require analysis
switch (name) {
case 'qs':
mod = require('qs')
break
case 'querystring':
mod = require('querystring')
break
}
// store to prevent invoking require()
parsers[name] = mod
return mod.parse
}
/**
* Get the simple query parser.
*
* @param {object} options
*/
function simpleparser (options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
var parse = parser('querystring')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse (body) {
var paramCount = parameterCount(body, parameterLimit)
if (paramCount === undefined) {
debug('too many parameters')
throw createError(413, 'too many parameters', {
type: 'parameters.too.many'
})
}
debug('parse urlencoding')
return parse(body, undefined, undefined, { maxKeys: parameterLimit })
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
-83
View File
@@ -1,83 +0,0 @@
'use strict'
/**
* Module dependencies.
*/
var bytes = require('bytes')
var contentType = require('content-type')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = {
getCharset,
normalizeOptions
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch {
return undefined
}
}
/**
* Get the simple type checker.
*
* @param {string | string[]} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
/**
* Normalizes the common options for all parsers.
*
* @param {object} options options to normalize
* @param {string | string[] | function} defaultType default content type(s) or a function to determine it
* @returns {object}
*/
function normalizeOptions (options, defaultType) {
if (!defaultType) {
// Parsers must define a default content type
throw new TypeError('defaultType must be provided')
}
var inflate = options?.inflate !== false
var limit = typeof options?.limit !== 'number'
? bytes.parse(options?.limit || '100kb')
: options?.limit
var type = options?.type || defaultType
var verify = options?.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
return {
inflate,
limit,
verify,
shouldParse
}
}