From 959956cfc99ce55005d085dc081de910bca18d3d Mon Sep 17 00:00:00 2001 From: larabr Date: Thu, 1 Feb 2024 09:42:16 +0100 Subject: [PATCH] Use Compression Stream API when available, drop `config.deflateLevel` (#1717) Breaking change: the `config.deflateLevel` is removed as the API does not accept a deflate level in input, and the setting is of limited importance. Plus, using compression is discouraged on security grounds. --- openpgp.d.ts | 1 - src/config/config.js | 5 --- src/packet/compressed_data.js | 59 ++++++++++++++++++++++++----------- test/general/config.js | 3 +- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/openpgp.d.ts b/openpgp.d.ts index 9fa6c4d3..702564fd 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -323,7 +323,6 @@ interface Config { preferredCompressionAlgorithm: enums.compression; showVersion: boolean; showComment: boolean; - deflateLevel: number; aeadProtect: boolean; allowUnauthenticatedMessages: boolean; allowUnauthenticatedStream: boolean; diff --git a/src/config/config.js b/src/config/config.js index 91f29014..b006a3f4 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -37,11 +37,6 @@ export default { * @property {Integer} compression Default compression algorithm {@link module:enums.compression} */ preferredCompressionAlgorithm: enums.compression.uncompressed, - /** - * @memberof module:config - * @property {Integer} deflateLevel Default zip/zlib compression level, between 1 and 9 - */ - deflateLevel: 6, /** * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. * This option is applicable to: diff --git a/src/packet/compressed_data.js b/src/packet/compressed_data.js index d5af3b71..9a46cd87 100644 --- a/src/packet/compressed_data.js +++ b/src/packet/compressed_data.js @@ -67,11 +67,6 @@ class CompressedDataPacket { * @type {Uint8Array | ReadableStream} */ this.compressed = null; - - /** - * zip/zlib compression level, between 1 and 9 - */ - this.deflateLevel = config.deflateLevel; } /** @@ -131,7 +126,7 @@ class CompressedDataPacket { throw new Error(`${compressionName} compression not supported`); } - this.compressed = compressionFn(this.packets.write(), this.deflateLevel); + this.compressed = compressionFn(this.packets.write()); } } @@ -143,16 +138,18 @@ export default CompressedDataPacket; // // ////////////////////////// -function uncompressed(data) { - return data; -} - -function fflate_zlib(ZlibStreamedConstructor, options) { +/** + * Zlib processor relying on Compression Stream API if available, or falling back to fflate otherwise. + * @param {function(): CompressionStream|function(): DecompressionStream} compressionStreamInstantiator + * @param {FunctionConstructor} ZlibStreamedConstructor - fflate constructor + * @returns {ReadableStream} compressed or decompressed data + */ +function zlib(compressionStreamInstantiator, ZlibStreamedConstructor) { return data => { if (!util.isStream(data) || stream.isArrayStream(data)) { return stream.fromAsync(() => stream.readToEnd(data).then(inputData => { return new Promise((resolve, reject) => { - const zlibStream = new ZlibStreamedConstructor(options); + const zlibStream = new ZlibStreamedConstructor(); zlibStream.ondata = processedData => { resolve(processedData); }; @@ -165,8 +162,22 @@ function fflate_zlib(ZlibStreamedConstructor, options) { })); } + // Use Compression Streams API if available (see https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API) + if (compressionStreamInstantiator) { + try { + const compressorOrDecompressor = compressionStreamInstantiator(); + return data.pipeThrough(compressorOrDecompressor); + } catch (err) { + // If format is unsupported in Compression/DecompressionStream, then a TypeError in thrown, and we fallback to fflate. + if (err.name !== 'TypeError') { + throw err; + } + } + } + + // JS fallback const inputReader = data.getReader(); - const zlibStream = new ZlibStreamedConstructor(options); + const zlibStream = new ZlibStreamedConstructor(); return new ReadableStream({ async start(controller) { @@ -197,15 +208,27 @@ function bzip2(func) { }; } +/** + * Get Compression Stream API instatiators if the constructors are implemented. + * NB: the return instatiator functions will throw when called if the provided `compressionFormat` is not supported + * (supported formats cannot be determined in advance). + * @param {'deflate-raw'|'deflate'|'gzip'|string} compressionFormat + * @returns {{ compressor: function(): CompressionStream | false, decompressor: function(): DecompressionStream | false }} + */ +const getCompressionStreamInstantiators = compressionFormat => ({ + compressor: typeof CompressionStream !== 'undefined' && (() => new CompressionStream(compressionFormat)), + decompressor: typeof DecompressionStream !== 'undefined' && (() => new DecompressionStream(compressionFormat)) +}); + const compress_fns = { - zip: /*#__PURE__*/ (compressed, level) => fflate_zlib(Deflate, { level })(compressed), - zlib: /*#__PURE__*/ (compressed, level) => fflate_zlib(Zlib, { level })(compressed) + zip: /*#__PURE__*/ zlib(getCompressionStreamInstantiators('deflate-raw').compressor, Deflate), + zlib: /*#__PURE__*/ zlib(getCompressionStreamInstantiators('deflate').compressor, Zlib) }; const decompress_fns = { - uncompressed: uncompressed, - zip: /*#__PURE__*/ fflate_zlib(Inflate), - zlib: /*#__PURE__*/ fflate_zlib(Unzlib), + uncompressed: data => data, + zip: /*#__PURE__*/ zlib(getCompressionStreamInstantiators('deflate-raw').decompressor, Inflate), + zlib: /*#__PURE__*/ zlib(getCompressionStreamInstantiators('deflate').decompressor, Unzlib), bzip2: /*#__PURE__*/ bzip2(BunzipDecode) }; diff --git a/test/general/config.js b/test/general/config.js index 55120a30..34e8a1fa 100644 --- a/test/general/config.js +++ b/test/general/config.js @@ -278,8 +278,7 @@ n9/quqtmyOtYOA6gXNCw0Fal3iANKBmsPmYI const config = { aeadProtect: true, - preferredCompressionAlgorithm: openpgp.enums.compression.zip, - deflateLevel: 1 + preferredCompressionAlgorithm: openpgp.enums.compression.zip }; const armored2 = await openpgp.encrypt({ message, passwords, config }); const encrypted2 = await openpgp.readMessage({ armoredMessage: armored2 });