diff --git a/README.md b/README.md index d7149004..381a3523 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ This module provides the following parsers: * [Text body parser](#bodyparsertextoptions) * [URL-encoded form body parser](#bodyparserurlencodedoptions) +All parsers support automatic inflation of `gzip`, `br` (brotli), `deflate` +and `zstd` encodings. `zstd` is available in Node.js `^22.15.0` and +`>=23.8.0`. + Other body parsers you might be interested in: - [body](https://www.npmjs.com/package/body#readme) @@ -72,8 +76,7 @@ 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`, -`br` (brotli) and `deflate` encodings. +Unicode encoding of the body. A new `body` object containing the parsed data is populated on the `request` object after the middleware (i.e. `req.body`). @@ -133,9 +136,7 @@ encoding of the request. The parsing can be aborted by throwing an error. ### bodyParser.raw([options]) 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`, `br` (brotli) and `deflate` -encodings. +requests where the `Content-Type` header matches the `type` option. 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 @@ -181,9 +182,7 @@ encoding of the request. The parsing can be aborted by throwing an error. ### bodyParser.text([options]) 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`, `br` (brotli) and `deflate` -encodings. +requests where the `Content-Type` header matches the `type` option. 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 @@ -234,8 +233,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 and ISO-8859-1 encodings of the body and supports -automatic inflation of `gzip`, `br` (brotli) and `deflate` encodings. +parser accepts only UTF-8 and ISO-8859-1 encodings of the body. 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 bf1a1df4..9a449041 100644 --- a/lib/read.js +++ b/lib/read.js @@ -25,6 +25,12 @@ const { getCharset } = require('./utils') module.exports = read +/** + * @const + * whether current node version has zstandard support + */ +const hasZstandardSupport = 'createZstdDecompress' in zlib + /** * Read a request into a buffer and parse. * @@ -222,12 +228,16 @@ function createDecompressionStream (encoding, debug) { case 'br': debug('brotli decompress body') return zlib.createBrotliDecompress() - default: - throw createError(415, 'unsupported content encoding "' + encoding + '"', { - encoding: encoding, - type: 'encoding.unsupported' - }) + case 'zstd': + if (hasZstandardSupport) { + debug('zstd decompress body') + return zlib.createZstdDecompress() + } } + throw createError(415, 'unsupported content encoding "' + encoding + '"', { + encoding: encoding, + type: 'encoding.unsupported' + }) } /** diff --git a/test/json.js b/test/json.js index 1fd30f46..44495a30 100644 --- a/test/json.js +++ b/test/json.js @@ -3,10 +3,15 @@ const assert = require('node:assert') const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage const http = require('node:http') +const zlib = require('node:zlib') const request = require('supertest') const bodyParser = require('..') +const hasZstandardSupport = 'createZstdDecompress' in zlib +const zstandardit = hasZstandardSupport ? it : it.skip +const nozstandardit = !hasZstandardSupport ? it : it.skip + describe('bodyParser.json()', function () { it('should parse JSON', function (done) { request(createServer()) @@ -702,6 +707,24 @@ describe('bodyParser.json()', function () { test.expect(200, '{"name":"论"}', done) }) + zstandardit('should support zstandard encoding', function (done) { + const server = createServer({ experimentalZstd: true, limit: '1kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('28b52ffd200e7100007b226e616d65223a22e8aeba227d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + nozstandardit('should throw 415 if there\'s no zstandard support', function (done) { + const server = createServer({ experimentalZstd: true, limit: '1kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('28b52ffd200e7100007b226e616d65223a22e8aeba227d', 'hex')) + test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done) + }) + it('should be case-insensitive', function (done) { const test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/raw.js b/test/raw.js index 9ffe2e31..1b820141 100644 --- a/test/raw.js +++ b/test/raw.js @@ -3,10 +3,15 @@ const assert = require('node:assert') const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage const http = require('node:http') +const zlib = require('node:zlib') const request = require('supertest') const bodyParser = require('..') +const hasZstandardSupport = 'createZstdDecompress' in zlib +const zstandardit = hasZstandardSupport ? it : it.skip +const nozstandardit = !hasZstandardSupport ? it : it.skip + describe('bodyParser.raw()', function () { before(function () { this.server = createServer() @@ -458,6 +463,24 @@ describe('bodyParser.raw()', function () { test.expect(200, 'buf:6e616d653de8aeba', done) }) + zstandardit('should support zstandard encoding', function (done) { + const server = createServer({ experimentalZstd: true, limit: '10kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex')) + test.expect(200, 'buf:6e616d653de8aeba', done) + }) + + nozstandardit('should throw 415 if there\'s no zstandard support', function (done) { + const server = createServer({ experimentalZstd: true, limit: '10kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex')) + test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done) + }) + it('should be case-insensitive', function (done) { const test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/text.js b/test/text.js index 2b82db87..0a679eb0 100644 --- a/test/text.js +++ b/test/text.js @@ -3,10 +3,15 @@ const assert = require('node:assert') const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage const http = require('node:http') +const zlib = require('node:zlib') const request = require('supertest') const bodyParser = require('..') +const hasZstandardSupport = 'createZstdDecompress' in zlib +const zstandardit = hasZstandardSupport ? it : it.skip +const nozstandardit = !hasZstandardSupport ? it : it.skip + describe('bodyParser.text()', function () { before(function () { this.server = createServer() @@ -528,6 +533,24 @@ describe('bodyParser.text()', function () { test.expect(200, '"name is 论"', done) }) + zstandardit('should support zstandard encoding', function (done) { + const server = createServer({ experimentalZstd: true, limit: '10kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('28b52ffd200b5900006e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + nozstandardit('should throw 415 if there\'s no zstandard support', function (done) { + const server = createServer({ experimentalZstd: true, limit: '10kb' }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('28b52ffd200b5900006e616d6520697320e8aeba', 'hex')) + test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done) + }) + it('should be case-insensitive', function (done) { const test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/urlencoded.js b/test/urlencoded.js index 02ab39eb..f7ec8173 100644 --- a/test/urlencoded.js +++ b/test/urlencoded.js @@ -3,10 +3,15 @@ const assert = require('node:assert') const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage const http = require('node:http') +const zlib = require('node:zlib') const request = require('supertest') const bodyParser = require('..') +const hasZstandardSupport = 'createZstdDecompress' in zlib +const zstandardit = hasZstandardSupport ? it : it.skip +const nozstandardit = !hasZstandardSupport ? it : it.skip + describe('bodyParser.urlencoded()', function () { before(function () { this.server = createServer() @@ -910,6 +915,24 @@ describe('bodyParser.urlencoded()', function () { test.expect(200, '{"name":"论"}', done) }) + zstandardit('should support zstandard encoding', function (done) { + const server = createServer({ experimentalZstd: true }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + nozstandardit('should throw 415 if there\'s no zstandard support', function (done) { + const server = createServer({ experimentalZstd: true }) + const test = request(server).post('/') + test.set('Content-Encoding', 'zstd') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex')) + test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done) + }) + it('should be case-insensitive', function (done) { const test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP')