Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ unreleased

* fix: use static exports instead of lazy getters to improve ESM compatibility
* feat: add subpath exports for individual parsers
* feat: JSON parser now only accepts UTF-8 encoding per RFC 8259 section 8.1

2.2.2 / 2026-01-07
=========================
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ The various errors returned by this module are described in the
### bodyParser.json([options])

Returns middleware that only parses `json` and only looks at requests where
the `Content-Type` header matches the `type` option. This parser accepts any
Unicode encoding of the body and supports automatic inflation of `gzip`,
`br` (brotli) and `deflate` encodings.
the `Content-Type` header matches the `type` option. This parser only accepts
UTF-8 encoding of the body per [RFC 8259 section 8.1](https://datatracker.ietf.org/doc/html/rfc8259#section-8.1)
and supports automatic inflation of `gzip`, `br` (brotli) and `deflate` encodings.

A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`).
Expand Down
5 changes: 2 additions & 3 deletions lib/types/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

var debug = require('debug')('body-parser:json')
var read = require('../read')
var { normalizeOptions } = require('../utils')
var { normalizeOptions, isValidJsonCharset } = require('../utils')

/**
* Module exports.
Expand Down Expand Up @@ -80,8 +80,7 @@ function json (options) {

const readOptions = {
...normalizedOptions,
// assert charset per RFC 7159 sec 8.1
isValidCharset: (charset) => charset.slice(0, 4) === 'utf-'
isValidCharset: isValidJsonCharset
}

return function jsonParser (req, res, next) {
Expand Down
5 changes: 2 additions & 3 deletions lib/types/urlencoded.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var createError = require('http-errors')
var debug = require('debug')('body-parser:urlencoded')
var read = require('../read')
var qs = require('qs')
var { normalizeOptions } = require('../utils')
var { normalizeOptions, isValidUrlencodedCharset } = require('../utils')

/**
* Module exports.
Expand All @@ -43,8 +43,7 @@ function urlencoded (options) {

const readOptions = {
...normalizedOptions,
// assert charset
isValidCharset: (charset) => charset === 'utf-8' || charset === 'iso-8859-1'
isValidCharset: isValidUrlencodedCharset
}

return function urlencodedParser (req, res, next) {
Expand Down
32 changes: 31 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ var typeis = require('type-is')
module.exports = {
getCharset,
normalizeOptions,
passthrough
passthrough,
isValidJsonCharset,
isValidUrlencodedCharset
}

/**
Expand Down Expand Up @@ -96,3 +98,31 @@ function normalizeOptions (options, defaultType) {
function passthrough (value) {
return value
}

/**
* Determines whether a charset is permitted for application/json payloads.
*
* Per RFC 8259 section 8.1, JSON text exchanged between systems that are not part of a closed
* ecosystem MUST be encoded using UTF-8 [RFC 3629].
* Src: https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
*
* @param {string | null | undefined} charset lowercase charset label
* @returns {boolean}
* @private
*/
function isValidJsonCharset (charset) {
return charset === 'utf-8'
}

/**
* Determines whether a charset is permitted for application/x-www-form-urlencoded payloads.
*
* Per HTML Living Standard, the only supported encodings are UTF-8 and ISO-8859-1.
*
* @param {string | null | undefined} charset lowercase charset label
* @returns {boolean}
* @private
*/
function isValidUrlencodedCharset (charset) {
return charset === 'utf-8' || charset === 'iso-8859-1'
}
21 changes: 4 additions & 17 deletions test/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,19 +496,6 @@ describe('bodyParser.json()', function () {
.expect(200, '{"user":"tobi"}', done)
})

it('should work with different charsets', function (done) {
var server = createServer({
verify: function (req, res, buf) {
if (buf[0] === 0x5b) throw new Error('no arrays')
}
})

var test = request(server).post('/')
test.set('Content-Type', 'application/json; charset=utf-16')
test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
test.expect(200, '{"name":"论"}', done)
})

it('should 415 on unknown charset prior to verify', function (done) {
var server = createServer({
verify: function (req, res, buf) {
Expand Down Expand Up @@ -621,18 +608,18 @@ describe('bodyParser.json()', function () {
test.expect(200, '{"name":"论"}', done)
})

it('should parse utf-16', function (done) {
it('should fail on utf-16', function (done) {
var test = request(this.server).post('/')
test.set('Content-Type', 'application/json; charset=utf-16')
test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
test.expect(200, '{"name":"论"}', done)
test.expect(415, '[charset.unsupported] unsupported charset "UTF-16"', done)
})

it('should parse utf-32', function (done) {
it('should fail on utf-32', function (done) {
var test = request(this.server).post('/')
test.set('Content-Type', 'application/json; charset=utf-32')
test.write(Buffer.from('fffe00007b000000220000006e000000610000006d00000065000000220000003a00000022000000ba8b0000220000007d000000', 'hex'))
test.expect(200, '{"name":"论"}', done)
test.expect(415, '[charset.unsupported] unsupported charset "UTF-32"', done)
})

it('should parse when content-length != char length', function (done) {
Expand Down
59 changes: 57 additions & 2 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const assert = require('node:assert')
const { normalizeOptions } = require('../lib/utils.js')
const assert = require('node:assert/strict')
const { normalizeOptions, isValidJsonCharset, isValidUrlencodedCharset } = require('../lib/utils.js')

describe('normalizeOptions(options, defaultType)', () => {
it('should return default options when no options are provided', () => {
Expand Down Expand Up @@ -161,3 +161,58 @@ describe('normalizeOptions(options, defaultType)', () => {
})
})
})

describe('isValidJsonCharset(charset)', () => {
it('should return false for missing/empty values', () => {
assert.equal(isValidJsonCharset(), false)
assert.equal(isValidJsonCharset(undefined), false)
assert.equal(isValidJsonCharset(null), false)
assert.equal(isValidJsonCharset(''), false)
})

it('should return true for "utf-8"', () => {
assert.equal(isValidJsonCharset('utf-8'), true)
})

it('should return false for other utf-* labels (not allowed by RFC 8259)', () => {
assert.equal(isValidJsonCharset('utf-7'), false)
assert.equal(isValidJsonCharset('utf-16'), false)
assert.equal(isValidJsonCharset('utf-32'), false)
assert.equal(isValidJsonCharset('utf-16le'), false)
assert.equal(isValidJsonCharset('utf-16be'), false)
assert.equal(isValidJsonCharset('utf-32le'), false)
assert.equal(isValidJsonCharset('utf-32be'), false)
assert.equal(isValidJsonCharset('utf-1'), false)
})

it('should return false for non-JSON charsets', () => {
assert.equal(isValidJsonCharset('us-ascii'), false)
assert.equal(isValidJsonCharset('iso-8859-1'), false)
assert.equal(isValidJsonCharset('windows-1252'), false)
})
})

describe('isValidUrlencodedCharset(charset)', () => {
it('should return false for missing/empty values', () => {
assert.equal(isValidUrlencodedCharset(), false)
assert.equal(isValidUrlencodedCharset(undefined), false)
assert.equal(isValidUrlencodedCharset(null), false)
assert.equal(isValidUrlencodedCharset(''), false)
})

it('should return true for "utf-8" and "iso-8859-1"', () => {
assert.equal(isValidUrlencodedCharset('utf-8'), true)
assert.equal(isValidUrlencodedCharset('iso-8859-1'), true)
})

it('should return false for other UTF encodings', () => {
assert.equal(isValidUrlencodedCharset('utf-16'), false)
assert.equal(isValidUrlencodedCharset('utf-32'), false)
})

it('should return false for non-form encodings', () => {
assert.equal(isValidUrlencodedCharset('us-ascii'), false)
assert.equal(isValidUrlencodedCharset('windows-1252'), false)
assert.equal(isValidUrlencodedCharset('shift_jis'), false)
})
})