Skip to content

Commit

Permalink
Added support for brotli ('br') content-encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgindi authored and wesleytodd committed Aug 17, 2024
1 parent 6cea6bd commit 0d06833
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 13 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ before trusting. For example, `req.body.foo.toString()` may fail in multiple
ways, for example the `foo` property may not be there or may not be a string,
and `toString` may not be a function and instead a string or other user input.

**Note** Brotli is supported only since Node.js versions v11.7.0 and v10.16.0.

[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).

_This does not handle multipart bodies_, due to their complex and typically
Expand Down Expand Up @@ -64,8 +66,8 @@ The various errors returned by this module are described in the

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` and
`deflate` encodings.
Unicode encoding of the body 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 Expand Up @@ -119,7 +121,8 @@ encoding of the request. The parsing can be aborted by throwing an error.

Returns middleware that parses all bodies as a `Buffer` and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip` and `deflate` encodings.
parser 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`). This will be a `Buffer` object
Expand Down Expand Up @@ -164,7 +167,8 @@ encoding of the request. The parsing can be aborted by throwing an error.

Returns middleware that parses all bodies as a string and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip` and `deflate` encodings.
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
encodings.

A new `body` string containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This will be a string of the
Expand Down Expand Up @@ -214,7 +218,7 @@ encoding of the request. The parsing can be aborted by throwing an error.
Returns middleware that only parses `urlencoded` bodies and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser accepts only UTF-8 encoding of the body and supports automatic
inflation of `gzip` and `deflate` encodings.
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`). This object will contain
Expand Down
25 changes: 20 additions & 5 deletions lib/read.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ var zlib = require('zlib')

module.exports = read

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliDecompress' in zlib

/**
* Read a request into a buffer and parse.
*
Expand Down Expand Up @@ -174,11 +180,20 @@ function contentstream (req, debug, inflate) {
stream = req
stream.length = length
break
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
case 'br':
if (hasBrotliSupport) {
stream = zlib.createBrotliDecompress()
debug('brotli decompress body')
req.pipe(stream)
}
break
}

if (stream === undefined) {
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}

return stream
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
"node": ">= 0.10"
},
"scripts": {
"lint": "eslint .",
"lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
"test-ci": "nyc --reporter=html --reporter=text npm test",
"test-travis": "nyc --reporter=text npm test"
}
}
24 changes: 24 additions & 0 deletions test/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
? describe
: describe.skip

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')

describe('bodyParser.json()', function () {
it('should parse JSON', function (done) {
request(createServer())
Expand Down Expand Up @@ -683,6 +689,24 @@ describe('bodyParser.json()', function () {
test.expect(200, '{"name":"论"}', done)
})

var brotlit = hasBrotliSupport ? it : it.skip
brotlit('should support brotli encoding', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/json')
test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex'))
test.expect(200, '{"name":"论"}', done)
})

var nobrotlit = hasBrotliSupport ? it.skip : it
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/json')
test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex'))
test.expect(415, 'unsupported content encoding "br"', done)
})

it('should be case-insensitive', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'GZIP')
Expand Down
24 changes: 24 additions & 0 deletions test/raw.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
? describe
: describe.skip

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')

describe('bodyParser.raw()', function () {
before(function () {
this.server = createServer()
Expand Down Expand Up @@ -455,6 +461,24 @@ describe('bodyParser.raw()', function () {
test.expect(200, 'buf:6e616d653de8aeba', done)
})

var brotlit = hasBrotliSupport ? it : it.skip
brotlit('should support brotli encoding', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
test.expect(200, 'buf:6e616d653de8aeba', done)
})

var nobrotlit = hasBrotliSupport ? it.skip : it
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
test.expect(415, 'unsupported content encoding "br"', done)
})

it('should be case-insensitive', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'GZIP')
Expand Down
24 changes: 24 additions & 0 deletions test/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
? describe
: describe.skip

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')

describe('bodyParser.text()', function () {
before(function () {
this.server = createServer()
Expand Down Expand Up @@ -525,6 +531,24 @@ describe('bodyParser.text()', function () {
test.expect(200, '"name is 论"', done)
})

var brotlit = hasBrotliSupport ? it : it.skip
brotlit('should support brotli encoding', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'text/plain')
test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex'))
test.expect(200, '"name is 论"', done)
})

var nobrotlit = hasBrotliSupport ? it.skip : it
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'text/plain')
test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex'))
test.expect(415, 'unsupported content encoding "br"', done)
})

it('should be case-insensitive', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'GZIP')
Expand Down
24 changes: 24 additions & 0 deletions test/urlencoded.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
? describe
: describe.skip

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')

describe('bodyParser.urlencoded()', function () {
before(function () {
this.server = createServer()
Expand Down Expand Up @@ -831,6 +837,24 @@ describe('bodyParser.urlencoded()', function () {
test.expect(200, '{"name":"论"}', done)
})

var brotlit = hasBrotliSupport ? it : it.skip
brotlit('should support brotli encoding', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/x-www-form-urlencoded')
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
test.expect(200, '{"name":"论"}', done)
})

var nobrotlit = hasBrotliSupport ? it.skip : it
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'br')
test.set('Content-Type', 'application/x-www-form-urlencoded')
test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex'))
test.expect(415, 'unsupported content encoding "br"', done)
})

it('should be case-insensitive', function (done) {
var test = request(this.server).post('/')
test.set('Content-Encoding', 'GZIP')
Expand Down

0 comments on commit 0d06833

Please sign in to comment.