From 07ce14d6c1708cae18a25be0c44f4eb5a6b063e3 Mon Sep 17 00:00:00 2001 From: Daniel Cohen Gindi Date: Sat, 17 Aug 2024 21:42:21 +0300 Subject: [PATCH] Added support for brotli ('br') content-encoding (#406) --- HISTORY.md | 5 +++++ README.md | 12 +++++++----- lib/read.js | 25 ++++++++++++++++++++----- test/json.js | 20 ++++++++++++++++++++ test/raw.js | 20 ++++++++++++++++++++ test/text.js | 20 ++++++++++++++++++++ test/urlencoded.js | 20 ++++++++++++++++++++ 7 files changed, 112 insertions(+), 10 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index ea0e0dae..1aad1773 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,8 @@ +unreleased +========================= + +* add brotli support #406 + 2.0.0-beta.2 / 2023-02-23 ========================= diff --git a/README.md b/README.md index 219d63c9..740f4e2c 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,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`). @@ -119,7 +119,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 @@ -164,7 +165,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 @@ -214,7 +216,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 diff --git a/lib/read.js b/lib/read.js index 20a18ac8..a4bd446d 100644 --- a/lib/read.js +++ b/lib/read.js @@ -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. * @@ -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 diff --git a/test/json.js b/test/json.js index 1040d56b..3dcfd9c0 100644 --- a/test/json.js +++ b/test/json.js @@ -12,6 +12,10 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' ? describe : describe.skip +var hasBrotliSupport = 'createBrotliDecompress' in require('zlib') +var brotlit = hasBrotliSupport ? it : it.skip +var nobrotlit = !hasBrotliSupport ? it : it.skip + describe('bodyParser.json()', function () { it('should parse JSON', function (done) { request(createServer()) @@ -683,6 +687,22 @@ describe('bodyParser.json()', function () { test.expect(200, '{"name":"论"}', done) }) + 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) + }) + + 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') diff --git a/test/raw.js b/test/raw.js index fd2f3326..bd0b564c 100644 --- a/test/raw.js +++ b/test/raw.js @@ -12,6 +12,10 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' ? describe : describe.skip +var hasBrotliSupport = 'createBrotliDecompress' in require('zlib') +var brotlit = hasBrotliSupport ? it : it.skip +var nobrotlit = !hasBrotliSupport ? it : it.skip + describe('bodyParser.raw()', function () { before(function () { this.server = createServer() @@ -455,6 +459,22 @@ describe('bodyParser.raw()', function () { test.expect(200, 'buf:6e616d653de8aeba', done) }) + 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) + }) + + 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') diff --git a/test/text.js b/test/text.js index ec35d9b4..4eae37ac 100644 --- a/test/text.js +++ b/test/text.js @@ -12,6 +12,10 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' ? describe : describe.skip +var hasBrotliSupport = 'createBrotliDecompress' in require('zlib') +var brotlit = hasBrotliSupport ? it : it.skip +var nobrotlit = !hasBrotliSupport ? it : it.skip + describe('bodyParser.text()', function () { before(function () { this.server = createServer() @@ -525,6 +529,22 @@ describe('bodyParser.text()', function () { test.expect(200, '"name is 论"', done) }) + 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) + }) + + 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') diff --git a/test/urlencoded.js b/test/urlencoded.js index 3258d094..5ce2ffc5 100644 --- a/test/urlencoded.js +++ b/test/urlencoded.js @@ -12,6 +12,10 @@ var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' ? describe : describe.skip +var hasBrotliSupport = 'createBrotliDecompress' in require('zlib') +var brotlit = hasBrotliSupport ? it : it.skip +var nobrotlit = !hasBrotliSupport ? it : it.skip + describe('bodyParser.urlencoded()', function () { before(function () { this.server = createServer() @@ -831,6 +835,22 @@ describe('bodyParser.urlencoded()', function () { test.expect(200, '{"name":"论"}', done) }) + 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) + }) + + 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')