Skip to content

Commit ca9864a

Browse files
authored
refactor: move common request validation to read function (#600)
1 parent 0dad12f commit ca9864a

9 files changed

Lines changed: 90 additions & 176 deletions

File tree

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
unreleased
22
=========================
33

4+
* refactor: move common request validation to read function
45
* deps:
56
* type-is@^2.0.1
67

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ object after the middleware (i.e. `req.body`).
7676
The `json` function takes an optional `options` object that may contain any of
7777
the following keys:
7878

79+
##### defaultCharset
80+
81+
Specify the default character set for the json content if the charset is not
82+
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
83+
7984
##### inflate
8085

8186
When set to `true`, then deflated (compressed) bodies will be inflated; when
@@ -291,7 +296,7 @@ Whether to decode numeric entities such as `☺` when parsing an iso-8859-1
291296
form. Defaults to `false`.
292297

293298

294-
#### depth
299+
##### depth
295300

296301
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
297302

lib/read.js

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ var getBody = require('raw-body')
1616
var iconv = require('iconv-lite')
1717
var onFinished = require('on-finished')
1818
var zlib = require('node:zlib')
19+
var hasBody = require('type-is').hasBody
20+
var { getCharset } = require('./utils')
1921

2022
/**
2123
* Module exports.
@@ -36,14 +38,52 @@ module.exports = read
3638
*/
3739

3840
function read (req, res, next, parse, debug, options) {
41+
if (onFinished.isFinished(req)) {
42+
debug('body already parsed')
43+
next()
44+
return
45+
}
46+
47+
if (!('body' in req)) {
48+
req.body = undefined
49+
}
50+
51+
// skip requests without bodies
52+
if (!hasBody(req)) {
53+
debug('skip empty body')
54+
next()
55+
return
56+
}
57+
58+
debug('content-type %j', req.headers['content-type'])
59+
60+
// determine if request should be parsed
61+
if (!options.shouldParse(req)) {
62+
debug('skip parsing')
63+
next()
64+
return
65+
}
66+
67+
var encoding = null
68+
if (options?.skipCharset !== true) {
69+
encoding = getCharset(req) || options.defaultCharset
70+
71+
// validate charset
72+
if (!!options?.isValidCharset && !options.isValidCharset(encoding)) {
73+
debug('invalid charset')
74+
next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
75+
charset: encoding,
76+
type: 'charset.unsupported'
77+
}))
78+
return
79+
}
80+
}
81+
3982
var length
4083
var opts = options
4184
var stream
4285

4386
// read options
44-
var encoding = opts.encoding !== null
45-
? opts.encoding
46-
: null
4787
var verify = opts.verify
4888

4989
try {

lib/types/json.js

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@
1212
* @private
1313
*/
1414

15-
var createError = require('http-errors')
1615
var debug = require('debug')('body-parser:json')
17-
var isFinished = require('on-finished').isFinished
1816
var read = require('../read')
19-
var typeis = require('type-is')
20-
var { getCharset, normalizeOptions } = require('../utils')
17+
var { normalizeOptions } = require('../utils')
2118

2219
/**
2320
* Module exports.
@@ -51,7 +48,7 @@ var JSON_SYNTAX_REGEXP = /#+/g
5148
*/
5249

5350
function json (options) {
54-
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/json')
51+
var normalizedOptions = normalizeOptions(options, 'application/json')
5552

5653
var reviver = options?.reviver
5754
var strict = options?.strict !== false
@@ -84,49 +81,11 @@ function json (options) {
8481
}
8582

8683
return function jsonParser (req, res, next) {
87-
if (isFinished(req)) {
88-
debug('body already parsed')
89-
next()
90-
return
91-
}
92-
93-
if (!('body' in req)) {
94-
req.body = undefined
95-
}
96-
97-
// skip requests without bodies
98-
if (!typeis.hasBody(req)) {
99-
debug('skip empty body')
100-
next()
101-
return
102-
}
103-
104-
debug('content-type %j', req.headers['content-type'])
105-
106-
// determine if request should be parsed
107-
if (!shouldParse(req)) {
108-
debug('skip parsing')
109-
next()
110-
return
111-
}
112-
113-
// assert charset per RFC 7159 sec 8.1
114-
var charset = getCharset(req) || 'utf-8'
115-
if (charset.slice(0, 4) !== 'utf-') {
116-
debug('invalid charset')
117-
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
118-
charset: charset,
119-
type: 'charset.unsupported'
120-
}))
121-
return
122-
}
123-
124-
// read
12584
read(req, res, next, parse, debug, {
126-
encoding: charset,
127-
inflate,
128-
limit,
129-
verify
85+
...normalizedOptions,
86+
87+
// assert charset per RFC 7159 sec 8.1
88+
isValidCharset: (charset) => charset.slice(0, 4) === 'utf-'
13089
})
13190
}
13291
}

lib/types/raw.js

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
*/
1212

1313
var debug = require('debug')('body-parser:raw')
14-
var isFinished = require('on-finished').isFinished
1514
var read = require('../read')
16-
var typeis = require('type-is')
1715
var { normalizeOptions } = require('../utils')
1816

1917
/**
@@ -31,45 +29,18 @@ module.exports = raw
3129
*/
3230

3331
function raw (options) {
34-
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/octet-stream')
32+
var normalizedOptions = normalizeOptions(options, 'application/octet-stream')
3533

3634
function parse (buf) {
3735
return buf
3836
}
3937

4038
return function rawParser (req, res, next) {
41-
if (isFinished(req)) {
42-
debug('body already parsed')
43-
next()
44-
return
45-
}
46-
47-
if (!('body' in req)) {
48-
req.body = undefined
49-
}
50-
51-
// skip requests without bodies
52-
if (!typeis.hasBody(req)) {
53-
debug('skip empty body')
54-
next()
55-
return
56-
}
57-
58-
debug('content-type %j', req.headers['content-type'])
59-
60-
// determine if request should be parsed
61-
if (!shouldParse(req)) {
62-
debug('skip parsing')
63-
next()
64-
return
65-
}
66-
67-
// read
6839
read(req, res, next, parse, debug, {
69-
encoding: null,
70-
inflate,
71-
limit,
72-
verify
40+
...normalizedOptions,
41+
42+
// Skip charset validation and parse the body as is
43+
skipCharset: true
7344
})
7445
}
7546
}

lib/types/text.js

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111
*/
1212

1313
var debug = require('debug')('body-parser:text')
14-
var isFinished = require('on-finished').isFinished
1514
var read = require('../read')
16-
var typeis = require('type-is')
17-
var { getCharset, normalizeOptions } = require('../utils')
15+
var { normalizeOptions } = require('../utils')
1816

1917
/**
2018
* Module exports.
@@ -31,50 +29,13 @@ module.exports = text
3129
*/
3230

3331
function text (options) {
34-
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'text/plain')
35-
36-
var defaultCharset = options?.defaultCharset || 'utf-8'
32+
var normalizedOptions = normalizeOptions(options, 'text/plain')
3733

3834
function parse (buf) {
3935
return buf
4036
}
4137

4238
return function textParser (req, res, next) {
43-
if (isFinished(req)) {
44-
debug('body already parsed')
45-
next()
46-
return
47-
}
48-
49-
if (!('body' in req)) {
50-
req.body = undefined
51-
}
52-
53-
// skip requests without bodies
54-
if (!typeis.hasBody(req)) {
55-
debug('skip empty body')
56-
next()
57-
return
58-
}
59-
60-
debug('content-type %j', req.headers['content-type'])
61-
62-
// determine if request should be parsed
63-
if (!shouldParse(req)) {
64-
debug('skip parsing')
65-
next()
66-
return
67-
}
68-
69-
// get charset
70-
var charset = getCharset(req) || defaultCharset
71-
72-
// read
73-
read(req, res, next, parse, debug, {
74-
encoding: charset,
75-
inflate,
76-
limit,
77-
verify
78-
})
39+
read(req, res, next, parse, debug, normalizedOptions)
7940
}
8041
}

lib/types/urlencoded.js

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414

1515
var createError = require('http-errors')
1616
var debug = require('debug')('body-parser:urlencoded')
17-
var isFinished = require('on-finished').isFinished
1817
var read = require('../read')
19-
var typeis = require('type-is')
2018
var qs = require('qs')
21-
var { getCharset, normalizeOptions } = require('../utils')
19+
var { normalizeOptions } = require('../utils')
2220

2321
/**
2422
* Module exports.
@@ -35,10 +33,9 @@ module.exports = urlencoded
3533
*/
3634

3735
function urlencoded (options) {
38-
var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/x-www-form-urlencoded')
36+
var normalizedOptions = normalizeOptions(options, 'application/x-www-form-urlencoded')
3937

40-
var defaultCharset = options?.defaultCharset || 'utf-8'
41-
if (defaultCharset !== 'utf-8' && defaultCharset !== 'iso-8859-1') {
38+
if (normalizedOptions.defaultCharset !== 'utf-8' && normalizedOptions.defaultCharset !== 'iso-8859-1') {
4239
throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1')
4340
}
4441

@@ -52,49 +49,11 @@ function urlencoded (options) {
5249
}
5350

5451
return function urlencodedParser (req, res, next) {
55-
if (isFinished(req)) {
56-
debug('body already parsed')
57-
next()
58-
return
59-
}
60-
61-
if (!('body' in req)) {
62-
req.body = undefined
63-
}
64-
65-
// skip requests without bodies
66-
if (!typeis.hasBody(req)) {
67-
debug('skip empty body')
68-
next()
69-
return
70-
}
71-
72-
debug('content-type %j', req.headers['content-type'])
73-
74-
// determine if request should be parsed
75-
if (!shouldParse(req)) {
76-
debug('skip parsing')
77-
next()
78-
return
79-
}
80-
81-
// assert charset
82-
var charset = getCharset(req) || defaultCharset
83-
if (charset !== 'utf-8' && charset !== 'iso-8859-1') {
84-
debug('invalid charset')
85-
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
86-
charset: charset,
87-
type: 'charset.unsupported'
88-
}))
89-
return
90-
}
91-
92-
// read
9352
read(req, res, next, parse, debug, {
94-
encoding: charset,
95-
inflate,
96-
limit,
97-
verify
53+
...normalizedOptions,
54+
55+
// assert charset
56+
isValidCharset: (charset) => charset === 'utf-8' || charset === 'iso-8859-1'
9857
})
9958
}
10059
}

lib/utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function normalizeOptions (options, defaultType) {
6464
: options?.limit
6565
var type = options?.type || defaultType
6666
var verify = options?.verify || false
67+
var defaultCharset = options?.defaultCharset || 'utf-8'
6768

6869
if (verify !== false && typeof verify !== 'function') {
6970
throw new TypeError('option verify must be function')
@@ -78,6 +79,7 @@ function normalizeOptions (options, defaultType) {
7879
inflate,
7980
limit,
8081
verify,
82+
defaultCharset,
8183
shouldParse
8284
}
8385
}

0 commit comments

Comments
 (0)