Skip to content

Commit 69e7f71

Browse files
danielgindiwesleytodd
authored andcommitted
Added support for brotli ('br') content-encoding
1 parent 6cea6bd commit 69e7f71

File tree

7 files changed

+126
-11
lines changed

7 files changed

+126
-11
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ before trusting. For example, `req.body.foo.toString()` may fail in multiple
1616
ways, for example the `foo` property may not be there or may not be a string,
1717
and `toString` may not be a function and instead a string or other user input.
1818

19+
**Note** Brotli is supported only since Node.js versions v11.7.0 and v10.16.0.
20+
1921
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).
2022

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

6567
Returns middleware that only parses `json` and only looks at requests where
6668
the `Content-Type` header matches the `type` option. This parser accepts any
67-
Unicode encoding of the body and supports automatic inflation of `gzip` and
68-
`deflate` encodings.
69+
Unicode encoding of the body and supports automatic inflation of `gzip`,
70+
`br` (brotli) and `deflate` encodings.
6971

7072
A new `body` object containing the parsed data is populated on the `request`
7173
object after the middleware (i.e. `req.body`).
@@ -119,7 +121,8 @@ encoding of the request. The parsing can be aborted by throwing an error.
119121

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

124127
A new `body` object containing the parsed data is populated on the `request`
125128
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
@@ -164,7 +167,8 @@ encoding of the request. The parsing can be aborted by throwing an error.
164167

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

169173
A new `body` string containing the parsed data is populated on the `request`
170174
object after the middleware (i.e. `req.body`). This will be a string of the
@@ -214,7 +218,7 @@ encoding of the request. The parsing can be aborted by throwing an error.
214218
Returns middleware that only parses `urlencoded` bodies and only looks at
215219
requests where the `Content-Type` header matches the `type` option. This
216220
parser accepts only UTF-8 encoding of the body and supports automatic
217-
inflation of `gzip` and `deflate` encodings.
221+
inflation of `gzip`, `br` (brotli) and `deflate` encodings.
218222

219223
A new `body` object containing the parsed data is populated on the `request`
220224
object after the middleware (i.e. `req.body`). This object will contain

lib/read.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ var zlib = require('zlib')
2525

2626
module.exports = read
2727

28+
/**
29+
* @const
30+
* whether current node version has brotli support
31+
*/
32+
var hasBrotliSupport = 'createBrotliDecompress' in zlib
33+
2834
/**
2935
* Read a request into a buffer and parse.
3036
*
@@ -174,11 +180,20 @@ function contentstream (req, debug, inflate) {
174180
stream = req
175181
stream.length = length
176182
break
177-
default:
178-
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
179-
encoding: encoding,
180-
type: 'encoding.unsupported'
181-
})
183+
case 'br':
184+
if (hasBrotliSupport) {
185+
stream = zlib.createBrotliDecompress()
186+
debug('brotli decompress body')
187+
req.pipe(stream)
188+
}
189+
break
190+
}
191+
192+
if (stream === undefined) {
193+
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
194+
encoding: encoding,
195+
type: 'encoding.unsupported'
196+
})
182197
}
183198

184199
return stream

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@
4949
"lint": "eslint .",
5050
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
5151
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
52-
"test-cov": "nyc --reporter=html --reporter=text npm test"
52+
"test-cov": "nyc --reporter=text npm test"
5353
}
5454
}

test/json.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
1212
? describe
1313
: describe.skip
1414

15+
/**
16+
* @const
17+
* whether current node version has brotli support
18+
*/
19+
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')
20+
1521
describe('bodyParser.json()', function () {
1622
it('should parse JSON', function (done) {
1723
request(createServer())
@@ -683,6 +689,24 @@ describe('bodyParser.json()', function () {
683689
test.expect(200, '{"name":"论"}', done)
684690
})
685691

692+
var brotlit = hasBrotliSupport ? it : it.skip
693+
brotlit('should support brotli encoding', function (done) {
694+
var test = request(this.server).post('/')
695+
test.set('Content-Encoding', 'br')
696+
test.set('Content-Type', 'application/json')
697+
test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex'))
698+
test.expect(200, '{"name":"论"}', done)
699+
})
700+
701+
var nobrotlit = hasBrotliSupport ? it.skip : it
702+
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
703+
var test = request(this.server).post('/')
704+
test.set('Content-Encoding', 'br')
705+
test.set('Content-Type', 'application/json')
706+
test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex'))
707+
test.expect(415, 'unsupported content encoding "br"', done)
708+
})
709+
686710
it('should be case-insensitive', function (done) {
687711
var test = request(this.server).post('/')
688712
test.set('Content-Encoding', 'GZIP')

test/raw.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
1212
? describe
1313
: describe.skip
1414

15+
/**
16+
* @const
17+
* whether current node version has brotli support
18+
*/
19+
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')
20+
1521
describe('bodyParser.raw()', function () {
1622
before(function () {
1723
this.server = createServer()
@@ -455,6 +461,24 @@ describe('bodyParser.raw()', function () {
455461
test.expect(200, 'buf:6e616d653de8aeba', done)
456462
})
457463

464+
var brotlit = hasBrotliSupport ? it : it.skip
465+
brotlit('should support brotli encoding', function (done) {
466+
var test = request(this.server).post('/')
467+
test.set('Content-Encoding', 'br')
468+
test.set('Content-Type', 'application/octet-stream')
469+
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
470+
test.expect(200, 'buf:6e616d653de8aeba', done)
471+
})
472+
473+
var nobrotlit = hasBrotliSupport ? it.skip : it
474+
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
475+
var test = request(this.server).post('/')
476+
test.set('Content-Encoding', 'br')
477+
test.set('Content-Type', 'application/octet-stream')
478+
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
479+
test.expect(415, 'unsupported content encoding "br"', done)
480+
})
481+
458482
it('should be case-insensitive', function (done) {
459483
var test = request(this.server).post('/')
460484
test.set('Content-Encoding', 'GZIP')

test/text.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
1212
? describe
1313
: describe.skip
1414

15+
/**
16+
* @const
17+
* whether current node version has brotli support
18+
*/
19+
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')
20+
1521
describe('bodyParser.text()', function () {
1622
before(function () {
1723
this.server = createServer()
@@ -525,6 +531,24 @@ describe('bodyParser.text()', function () {
525531
test.expect(200, '"name is 论"', done)
526532
})
527533

534+
var brotlit = hasBrotliSupport ? it : it.skip
535+
brotlit('should support brotli encoding', function (done) {
536+
var test = request(this.server).post('/')
537+
test.set('Content-Encoding', 'br')
538+
test.set('Content-Type', 'text/plain')
539+
test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex'))
540+
test.expect(200, '"name is 论"', done)
541+
})
542+
543+
var nobrotlit = hasBrotliSupport ? it.skip : it
544+
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
545+
var test = request(this.server).post('/')
546+
test.set('Content-Encoding', 'br')
547+
test.set('Content-Type', 'text/plain')
548+
test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex'))
549+
test.expect(415, 'unsupported content encoding "br"', done)
550+
})
551+
528552
it('should be case-insensitive', function (done) {
529553
var test = request(this.server).post('/')
530554
test.set('Content-Encoding', 'GZIP')

test/urlencoded.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
1212
? describe
1313
: describe.skip
1414

15+
/**
16+
* @const
17+
* whether current node version has brotli support
18+
*/
19+
var hasBrotliSupport = 'createBrotliDecompress' in require('zlib')
20+
1521
describe('bodyParser.urlencoded()', function () {
1622
before(function () {
1723
this.server = createServer()
@@ -831,6 +837,24 @@ describe('bodyParser.urlencoded()', function () {
831837
test.expect(200, '{"name":"论"}', done)
832838
})
833839

840+
var brotlit = hasBrotliSupport ? it : it.skip
841+
brotlit('should support brotli encoding', function (done) {
842+
var test = request(this.server).post('/')
843+
test.set('Content-Encoding', 'br')
844+
test.set('Content-Type', 'application/x-www-form-urlencoded')
845+
test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex'))
846+
test.expect(200, '{"name":"论"}', done)
847+
})
848+
849+
var nobrotlit = hasBrotliSupport ? it.skip : it
850+
nobrotlit('should throw 415 if there\'s no brotli support', function (done) {
851+
var test = request(this.server).post('/')
852+
test.set('Content-Encoding', 'br')
853+
test.set('Content-Type', 'application/x-www-form-urlencoded')
854+
test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex'))
855+
test.expect(415, 'unsupported content encoding "br"', done)
856+
})
857+
834858
it('should be case-insensitive', function (done) {
835859
var test = request(this.server).post('/')
836860
test.set('Content-Encoding', 'GZIP')

0 commit comments

Comments
 (0)