diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7f3482e3..70eff19f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -20,6 +20,8 @@ jobs: ref: main path: main - uses: actions/setup-node@v3 + with: + node-version: '>=20.6.0' - name: Run pull request time benchmark run: cd pr && npm install && npm run --silent benchmark-time > benchmarks.txt && cat benchmarks.txt diff --git a/openpgp.d.ts b/openpgp.d.ts index 76a4072c..9fa6c4d3 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -7,9 +7,19 @@ * - Errietta Kostala */ -import type { WebStream as GenericWebStream, NodeStream as GenericNodeStream } from '@openpgp/web-stream-tools'; +import type { WebStream as GenericWebStream, NodeWebStream as GenericNodeWebStream } from '@openpgp/web-stream-tools'; -/* ############## v5 KEY #################### */ +/* ############## STREAM #################### */ +type Data = Uint8Array | string; +// web-stream-tools might end up supporting additional data types, so we re-declare the types +// to enforce the type contraint that we need. +export type WebStream = GenericWebStream; +export type NodeWebStream = GenericNodeWebStream; +export type Stream = WebStream | NodeWebStream; +export type MaybeStream = T | Stream; +type MaybeArray = T | Array; + +/* ############## KEY #################### */ // The Key and PublicKey types can be used interchangably since TS cannot detect the difference, as they have the same class properties. // The declared readKey(s) return type is Key instead of a PublicKey since it seems more obvious that a Key can be cast to a PrivateKey. export function readKey(options: { armoredKey: string, config?: PartialConfig }): Promise; @@ -118,13 +128,13 @@ export interface PrimaryUser { selfCertification: SignaturePacket; } -type AlgorithmInfo = { +export type AlgorithmInfo = { algorithm: enums.publicKeyNames; bits?: number; curve?: EllipticCurveName; }; -/* ############## v5 SIG #################### */ +/* ############## SIG #################### */ export function readSignature(options: { armoredSignature: string, config?: PartialConfig }): Promise; export function readSignature(options: { binarySignature: Uint8Array, config?: PartialConfig }): Promise; @@ -143,7 +153,7 @@ interface VerificationResult { signature: Promise; } -/* ############## v5 CLEARTEXT #################### */ +/* ############## CLEARTEXT #################### */ export function readCleartextMessage(options: { cleartextMessage: string, config?: PartialConfig }): Promise; @@ -176,7 +186,7 @@ export class CleartextMessage { verify(keys: PublicKey[], date?: Date, config?: Config): Promise; } -/* ############## v5 MSG #################### */ +/* ############## MSG #################### */ export function generateSessionKey(options: { encryptionKeys: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig }): Promise; export function encryptSessionKey(options: EncryptSessionKeyOptions & { format?: 'armored' }): Promise; export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'binary' }): Promise; @@ -191,24 +201,24 @@ export function createMessage>(options: { bina export function encrypt>(options: EncryptOptions & { message: Message, format?: 'armored' }): Promise< T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : string >; export function encrypt>(options: EncryptOptions & { message: Message, format: 'binary' }): Promise< T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : Uint8Array >; export function encrypt>(options: EncryptOptions & { message: Message, format: 'object' }): Promise>; export function sign>(options: SignOptions & { message: Message, format?: 'armored' }): Promise< T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : string >; export function sign>(options: SignOptions & { message: Message, format: 'binary' }): Promise< T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : Uint8Array >; export function sign>(options: SignOptions & { message: Message, format: 'object' }): Promise>; @@ -218,25 +228,25 @@ export function sign(options: SignOptions & { message: CleartextMessage, format: export function decrypt>(options: DecryptOptions & { message: Message, format: 'binary' }): Promise ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : Uint8Array }>; export function decrypt>(options: DecryptOptions & { message: Message }): Promise ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : string }>; export function verify(options: VerifyOptions & { message: CleartextMessage, format?: 'utf8' }): Promise>; export function verify>(options: VerifyOptions & { message: Message, format: 'binary' }): Promise ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : Uint8Array >>; export function verify>(options: VerifyOptions & { message: Message }): Promise ? WebStream : - T extends NodeStream ? NodeStream : + T extends NodeWebStream ? NodeWebStream : string >>; @@ -305,7 +315,7 @@ export class Message> { } -/* ############## v5 CONFIG #################### */ +/* ############## CONFIG #################### */ interface Config { preferredHashAlgorithm: enums.hash; @@ -347,11 +357,11 @@ export var config: Config; // PartialConfig has the same properties as Config, but declared as optional. // This interface is relevant for top-level functions, which accept a subset of configuration options -interface PartialConfig extends Partial {} +export interface PartialConfig extends Partial {} -/* ############## v5 PACKET #################### */ +/* ############## PACKET #################### */ -declare abstract class BasePacket { +export declare abstract class BasePacket { static readonly tag: enums.packet; public read(bytes: Uint8Array): void; public write(): Uint8Array; @@ -561,16 +571,7 @@ export class PacketList extends Array { public findPacket(tag: enums.packet): T | undefined; } -/* ############## v5 STREAM #################### */ - -type Data = Uint8Array | string; -export interface WebStream extends GenericWebStream {} -export interface NodeStream extends GenericNodeStream {} -export type Stream = WebStream | NodeStream; -export type MaybeStream = T | Stream; - -/* ############## v5 GENERAL #################### */ -type MaybeArray = T | Array; +/* ############## GENERAL #################### */ export interface UserID { name?: string; email?: string; comment?: string; } export interface SessionKey { @@ -586,7 +587,7 @@ export interface DecryptedSessionKey { export interface ReasonForRevocation { flag?: enums.reasonForRevocation, string?: string } -interface EncryptOptions { +export interface EncryptOptions { /** message to be encrypted as created by createMessage */ message: Message>; /** (optional) array of keys or single key, used to encrypt the message */ @@ -618,7 +619,7 @@ interface EncryptOptions { config?: PartialConfig; } -interface DecryptOptions { +export interface DecryptOptions { /** the message object with the encrypted data */ message: Message>; /** (optional) private keys with decrypted secret key data or session key */ @@ -640,7 +641,7 @@ interface DecryptOptions { config?: PartialConfig; } -interface SignOptions { +export interface SignOptions { message: CleartextMessage | Message>; signingKeys: MaybeArray; format?: 'armored' | 'binary' | 'object'; @@ -652,7 +653,7 @@ interface SignOptions { config?: PartialConfig; } -interface VerifyOptions { +export interface VerifyOptions { /** (cleartext) message object with signatures */ message: CleartextMessage | Message>; /** array of publicKeys or single key, to verify signatures */ @@ -668,7 +669,7 @@ interface VerifyOptions { config?: PartialConfig; } -interface EncryptSessionKeyOptions extends SessionKey { +export interface EncryptSessionKeyOptions extends SessionKey { encryptionKeys?: MaybeArray, passwords?: MaybeArray, format?: 'armored' | 'binary' | 'object', @@ -704,7 +705,7 @@ interface GenerateKeyOptions { } export type KeyOptions = GenerateKeyOptions; -interface SubkeyOptions { +export interface SubkeyOptions { type?: 'ecc' | 'rsa'; curve?: EllipticCurveName; rsaBits?: number; @@ -714,20 +715,20 @@ interface SubkeyOptions { config?: PartialConfig; } -declare class KeyID { +export declare class KeyID { bytes: string; equals(keyID: KeyID, matchWildcard?: boolean): boolean; toHex(): string; static fromID(hex: string): KeyID; } -interface DecryptMessageResult { +export interface DecryptMessageResult { data: MaybeStream; signatures: VerificationResult[]; filename: string; } -interface VerifyMessageResult = MaybeStream> { +export interface VerifyMessageResult = MaybeStream> { data: T; signatures: VerificationResult[]; } diff --git a/package-lock.json b/package-lock.json index d2c111e4..f1197d32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,10 @@ "@openpgp/noble-hashes": "^1.3.3-0", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", - "@openpgp/web-stream-tools": "^0.0.14", + "@openpgp/web-stream-tools": "~0.1.1", "@rollup/plugin-alias": "^5.0.0", "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.0", "@rollup/plugin-wasm": "^6.1.2", @@ -52,11 +52,11 @@ "rollup": "^3.29.4", "sinon": "^15.1.0", "ts-node": "^10.9.1", - "typescript": "^4.1.2", + "typescript": "^5.3.3", "web-streams-polyfill": "^3.2.0" }, "engines": { - "node": ">= 16.0.0" + "node": ">= 16.5.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -630,10 +630,13 @@ "dev": true }, "node_modules/@openpgp/web-stream-tools": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.14.tgz", - "integrity": "sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.1.1.tgz", + "integrity": "sha512-1WkV+z78S8DJNlUiCPxSjsna6gfAGCVuepL2KUDlBztXzhvplwWr4lAvsWcYzFkHykaFoCSOV9ssiRRq7QzydQ==", "dev": true, + "engines": { + "node": ">= 16.5.0" + }, "peerDependencies": { "typescript": ">=4.2" }, @@ -729,9 +732,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", - "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -745,7 +748,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" + "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -1004,10 +1007,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "13.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", - "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==", - "dev": true + "version": "20.11.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz", + "integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.3", @@ -3145,9 +3151,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -6486,16 +6492,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ua-parser-js": { @@ -6544,6 +6550,12 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -7247,9 +7259,9 @@ "dev": true }, "@openpgp/web-stream-tools": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.14.tgz", - "integrity": "sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.1.1.tgz", + "integrity": "sha512-1WkV+z78S8DJNlUiCPxSjsna6gfAGCVuepL2KUDlBztXzhvplwWr4lAvsWcYzFkHykaFoCSOV9ssiRRq7QzydQ==", "dev": true, "requires": {} }, @@ -7310,9 +7322,9 @@ } }, "@rollup/plugin-node-resolve": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", - "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "requires": { "@rollup/pluginutils": "^5.0.1", @@ -7531,10 +7543,13 @@ "dev": true }, "@types/node": { - "version": "13.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", - "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==", - "dev": true + "version": "20.11.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz", + "integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/normalize-package-data": { "version": "2.4.3", @@ -9186,9 +9201,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, @@ -11718,9 +11733,9 @@ } }, "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true }, "ua-parser-js": { @@ -11753,6 +11768,12 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", diff --git a/package.json b/package.json index f28f888e..edf883e4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "LGPL-3.0+", "homepage": "https://openpgpjs.org/", "engines": { - "node": ">= 16.0.0" + "node": ">= 16.5.0" }, "keywords": [ "crypto", @@ -68,10 +68,10 @@ "@openpgp/noble-hashes": "^1.3.3-0", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", - "@openpgp/web-stream-tools": "^0.0.14", + "@openpgp/web-stream-tools": "~0.1.1", "@rollup/plugin-alias": "^5.0.0", "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.0", "@rollup/plugin-wasm": "^6.1.2", @@ -102,7 +102,7 @@ "rollup": "^3.29.4", "sinon": "^15.1.0", "ts-node": "^10.9.1", - "typescript": "^4.1.2", + "typescript": "^5.3.3", "web-streams-polyfill": "^3.2.0" }, "dependencies": { diff --git a/src/openpgp.js b/src/openpgp.js index 408eb3e2..18f2ce09 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -287,7 +287,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords, if (!signingKeys) { signingKeys = []; } - const streaming = message.fromStream; + try { if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, signatureNotations, config); @@ -301,7 +301,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords, // serialize data const armor = format === 'armored'; const data = armor ? message.armor(config) : message.write(); - return convertStream(data, streaming, armor ? 'utf8' : 'binary'); + return convertStream(data); } catch (err) { throw util.wrapError('Error encrypting message', err); } @@ -372,7 +372,7 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys, }) ]); } - result.data = await convertStream(result.data, message.fromStream, format); + result.data = await convertStream(result.data); return result; } catch (err) { throw util.wrapError('Error decrypting message', err); @@ -438,7 +438,7 @@ export async function sign({ message, signingKeys, format = 'armored', detached ]); }); } - return convertStream(signature, message.fromStream, armor ? 'utf8' : 'binary'); + return convertStream(signature); } catch (err) { throw util.wrapError('Error signing message', err); } @@ -501,7 +501,7 @@ export async function verify({ message, verificationKeys, expectSigned = false, }) ]); } - result.data = await convertStream(result.data, message.fromStream, format); + result.data = await convertStream(result.data); return result; } catch (err) { throw util.wrapError('Error verifying signed message', err); @@ -672,22 +672,15 @@ function toArray(param) { /** * Convert data to or from Stream * @param {Object} data - the data to convert - * @param {'web'|'node'|false} streaming - Whether to return a ReadableStream, and of what type - * @param {'utf8'|'binary'} [encoding] - How to return data in Node Readable streams * @returns {Promise} The data in the respective format. * @async * @private */ -async function convertStream(data, streaming, encoding = 'utf8') { +async function convertStream(data) { const streamType = util.isStream(data); if (streamType === 'array') { return stream.readToEnd(data); } - if (streaming === 'node') { - data = stream.webToNode(data); - if (encoding !== 'binary') data.setEncoding(encoding); - return data; - } return data; } diff --git a/src/packet/compressed_data.js b/src/packet/compressed_data.js index 778027e7..d5af3b71 100644 --- a/src/packet/compressed_data.js +++ b/src/packet/compressed_data.js @@ -143,29 +143,10 @@ export default CompressedDataPacket; // // ////////////////////////// - -const nodeZlib = util.getNodeZlib(); - function uncompressed(data) { return data; } -function node_zlib(func, create, options = {}) { - return function (data) { - if (!util.isStream(data) || stream.isArrayStream(data)) { - return stream.fromAsync(() => stream.readToEnd(data).then(data => { - return new Promise((resolve, reject) => { - func(data, options, (err, result) => { - if (err) return reject(err); - resolve(result); - }); - }); - })); - } - return stream.nodeToWeb(stream.webToNode(data).pipe(create(options))); - }; -} - function fflate_zlib(ZlibStreamedConstructor, options) { return data => { if (!util.isStream(data) || stream.isArrayStream(data)) { @@ -216,20 +197,12 @@ function bzip2(func) { }; } -const compress_fns = nodeZlib ? { - zip: /*#__PURE__*/ (compressed, level) => node_zlib(nodeZlib.deflateRaw, nodeZlib.createDeflateRaw, { level })(compressed), - zlib: /*#__PURE__*/ (compressed, level) => node_zlib(nodeZlib.deflate, nodeZlib.createDeflate, { level })(compressed) -} : { +const compress_fns = { zip: /*#__PURE__*/ (compressed, level) => fflate_zlib(Deflate, { level })(compressed), zlib: /*#__PURE__*/ (compressed, level) => fflate_zlib(Zlib, { level })(compressed) }; -const decompress_fns = nodeZlib ? { - uncompressed: uncompressed, - zip: /*#__PURE__*/ node_zlib(nodeZlib.inflateRaw, nodeZlib.createInflateRaw), - zlib: /*#__PURE__*/ node_zlib(nodeZlib.inflate, nodeZlib.createInflate), - bzip2: /*#__PURE__*/ bzip2(BunzipDecode) -} : { +const decompress_fns = { uncompressed: uncompressed, zip: /*#__PURE__*/ fflate_zlib(Inflate), zlib: /*#__PURE__*/ fflate_zlib(Unzlib), diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 323ae475..50673b67 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -173,7 +173,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); const plaintextMessage = await openpgp.createMessage({ binary: inputStream }); assert(plaintextMessage.fromStream); @@ -183,10 +183,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => { @@ -199,7 +197,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -209,10 +207,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => { @@ -225,7 +221,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); const plaintextMessage = await openpgp.createMessage({ binary:inputStream }); assert(plaintextMessage.fromStream); @@ -235,10 +231,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => { @@ -251,7 +245,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -261,10 +255,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with streaming)', async () => { @@ -277,7 +269,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -287,10 +279,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with unauthenticated streaming)', async () => { @@ -303,7 +293,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -317,10 +307,8 @@ class MemoryBenchamrkSuite { config: { ...config, allowUnauthenticatedStream: true } }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); suite.add('openpgp.encrypt/decrypt (AEAD, text @ 10MB, with streaming)', async () => { @@ -333,7 +321,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); + const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -343,10 +331,8 @@ class MemoryBenchamrkSuite { assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption - await new Promise(resolve => { - decryptedData.pipe(require('fs').createWriteStream('/dev/null')); - decryptedData.on('end', resolve); - }); + const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null')); + await decryptedData.pipeTo(sink); }); const stats = await suite.run(); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 008cc713..1c4eb283 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -3840,9 +3840,7 @@ XfA3pqV4mTzF packets.push(message.packets.findPacket(openpgp.enums.packet.signature)); packets.push(message.packets.findPacket(openpgp.enums.packet.literalData)); verifyOpt.message = await openpgp.readMessage({ - binaryMessage: stream[ - globalThis.ReadableStream ? 'toStream' : 'webToNode' - ](packets.write()) + binaryMessage: stream.toStream(packets.write()) }); return openpgp.verify(verifyOpt); }).then(async function (verified) { diff --git a/test/general/streaming.js b/test/general/streaming.js index 3489656a..85761f57 100644 --- a/test/general/streaming.js +++ b/test/general/streaming.js @@ -416,7 +416,7 @@ function tests() { expect(stream.isStream(encrypted)).to.equal(expectedType); const message = await openpgp.readMessage({ - armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(encrypted, value => { + armoredMessage: stream.toStream(stream.transform(encrypted, value => { value += ''; const newlineIndex = value.indexOf('\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); @@ -453,7 +453,7 @@ function tests() { expect(stream.isStream(encrypted)).to.equal(expectedType); const message = await openpgp.readMessage({ - armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(encrypted, value => { + armoredMessage: stream.toStream(stream.transform(encrypted, value => { value += ''; const newlineIndex = value.indexOf('\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); @@ -486,7 +486,7 @@ function tests() { expect(stream.isStream(signed)).to.equal(expectedType); const message = await openpgp.readMessage({ - armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(signed, value => { + armoredMessage: stream.toStream(stream.transform(signed, value => { value += ''; const newlineIndex = value.indexOf('\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); @@ -872,6 +872,7 @@ function tests() { export default () => describe('Streaming', function() { let currentTest = 0; + const needsStreamPolyfills = !globalThis.ReadableStream; before(async function() { pubKey = await openpgp.readKey({ armoredKey: pub_key }); @@ -916,40 +917,41 @@ export default () => describe('Streaming', function() { tests(); - if (detectNode()) { + if (detectNode() && !needsStreamPolyfills) { // ReadableStream polyfills interfere with these tests const fs = util.nodeRequire('fs'); + const { Readable: NodeReadableStream } = util.nodeRequire('stream'); const { fileURLToPath } = util.nodeRequire('url'); const __filename = fileURLToPath(import.meta.url); it('Node: Encrypt and decrypt text message roundtrip', async function() { dataArrived(); // Do not wait until data arrived. const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js'), 'utf8'); // eslint-disable-line no-sync - const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'), { encoding: 'utf8' }); + const data = NodeReadableStream.toWeb(fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'), { encoding: 'utf8' })); const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: data }), passwords: ['test'] }); - expect(stream.isStream(encrypted)).to.equal('node'); + expect(stream.isStream(encrypted)).to.equal('web'); const message = await openpgp.readMessage({ armoredMessage: encrypted }); const decrypted = await openpgp.decrypt({ passwords: ['test'], message }); - expect(stream.isStream(decrypted.data)).to.equal('node'); + expect(stream.isStream(decrypted.data)).to.equal('web'); expect(await stream.readToEnd(decrypted.data)).to.equal(plaintext); }); it('Node: Encrypt and decrypt binary message roundtrip', async function() { dataArrived(); // Do not wait until data arrived. const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js')); // eslint-disable-line no-sync - const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js')); + const data = NodeReadableStream.toWeb(fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'))); const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: data }), passwords: ['test'], format: 'binary' }); - expect(stream.isStream(encrypted)).to.equal('node'); + expect(stream.isStream(encrypted)).to.equal('web'); const message = await openpgp.readMessage({ binaryMessage: encrypted }); const decrypted = await openpgp.decrypt({ @@ -957,7 +959,7 @@ export default () => describe('Streaming', function() { message, format: 'binary' }); - expect(stream.isStream(decrypted.data)).to.equal('node'); + expect(stream.isStream(decrypted.data)).to.equal('web'); expect(await stream.readToEnd(decrypted.data)).to.deep.equal(plaintext); }); } diff --git a/test/typescript/definitions.ts b/test/typescript/definitions.ts index 94983043..5258154d 100644 --- a/test/typescript/definitions.ts +++ b/test/typescript/definitions.ts @@ -7,6 +7,7 @@ */ import { ReadableStream as WebReadableStream } from 'web-streams-polyfill'; import { createReadStream } from 'fs'; +import { Readable as NodeNativeReadableStream } from 'stream'; import { expect } from 'chai'; import { @@ -15,7 +16,7 @@ import { encrypt, decrypt, sign, verify, config, enums, generateSessionKey, encryptSessionKey, decryptSessionKeys, LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage, - WebStream, NodeStream, + WebStream, NodeWebStream, } from 'openpgp'; (async () => { @@ -207,9 +208,9 @@ import { // Streaming - encrypt text message (armored output) try { - const nodeTextStream = createReadStream('non-existent-file', { encoding: 'utf8' }); + const nodeTextStream = NodeNativeReadableStream.toWeb(createReadStream('non-existent-file', { encoding: 'utf8' })); const messageFromNodeTextStream = await createMessage({ text: nodeTextStream }); - (await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeStream; + (await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeWebStream; } catch (err) {} const webTextStream = new WebReadableStream(); const messageFromWebTextStream = await createMessage({ text: webTextStream }); @@ -219,9 +220,9 @@ import { // Streaming - encrypt binary message (binary output) try { - const nodeBinaryStream = createReadStream('non-existent-file'); + const nodeBinaryStream = NodeNativeReadableStream.toWeb(createReadStream('non-existent-file')); const messageFromNodeBinaryStream = await createMessage({ binary: nodeBinaryStream }); - (await encrypt({ message: messageFromNodeBinaryStream, passwords: 'password', format: 'binary' })) as NodeStream; + (await encrypt({ message: messageFromNodeBinaryStream, passwords: 'password', format: 'binary' })) as NodeWebStream; } catch (err) {} const webBinaryStream = new WebReadableStream(); const messageFromWebBinaryStream = await createMessage({ binary: webBinaryStream }); diff --git a/test/unittests.js b/test/unittests.js index 54f63c3c..de16574c 100644 --- a/test/unittests.js +++ b/test/unittests.js @@ -27,7 +27,10 @@ globalThis.tryTests = function(name, tests, options) { }; globalThis.loadStreamsPolyfill = function() { - return import('web-streams-polyfill'); + // do not polyfill Node + const detectNodeWebStreams = () => typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object' && globalThis.ReadableStream; + + return detectNodeWebStreams() || import('web-streams-polyfill'); }; import runWorkerTests from './worker';