@patrickdk77/archive-defuse 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +345 -2
- package/dist/aes7z.d.ts +16 -0
- package/dist/aes7z.js +131 -0
- package/dist/alone.d.ts +21 -0
- package/dist/alone.js +65 -0
- package/dist/bcj2.d.ts +21 -0
- package/dist/bcj2.js +140 -0
- package/dist/bzip2.d.ts +21 -0
- package/dist/bzip2.js +688 -0
- package/dist/cab.d.ts +63 -0
- package/dist/cab.js +493 -0
- package/dist/checks.d.ts +23 -0
- package/dist/checks.js +123 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +364 -0
- package/dist/codecs.d.ts +39 -0
- package/dist/codecs.js +124 -0
- package/dist/coders.d.ts +58 -0
- package/dist/coders.js +392 -0
- package/dist/crc32.d.ts +8 -0
- package/dist/crc32.js +59 -0
- package/dist/encoder.d.ts +44 -0
- package/dist/encoder.js +114 -0
- package/dist/engine.d.ts +44 -0
- package/dist/engine.js +753 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +98 -0
- package/dist/filters.d.ts +34 -0
- package/dist/filters.js +385 -0
- package/dist/findings.d.ts +26 -0
- package/dist/findings.js +176 -0
- package/dist/formats.d.ts +14 -0
- package/dist/formats.js +97 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +50 -0
- package/dist/index.mjs +3 -0
- package/dist/inflate64.d.ts +6 -0
- package/dist/inflate64.js +388 -0
- package/dist/io.d.ts +78 -0
- package/dist/io.js +379 -0
- package/dist/lzma-formats.d.ts +75 -0
- package/dist/lzma-formats.js +373 -0
- package/dist/lzma-sniff.d.ts +20 -0
- package/dist/lzma-sniff.js +73 -0
- package/dist/lzma-writers.d.ts +35 -0
- package/dist/lzma-writers.js +276 -0
- package/dist/lzma1/LICENSE +201 -0
- package/dist/lzma1/NOTICE +36 -0
- package/dist/lzma1/crc32.d.ts +4 -0
- package/dist/lzma1/crc32.js +19 -0
- package/dist/lzma1/hash234.d.ts +22 -0
- package/dist/lzma1/hash234.js +75 -0
- package/dist/lzma1/hc4.d.ts +30 -0
- package/dist/lzma1/hc4.js +142 -0
- package/dist/lzma1/header.d.ts +32 -0
- package/dist/lzma1/header.js +73 -0
- package/dist/lzma1/lz-decoder.d.ts +41 -0
- package/dist/lzma1/lz-decoder.js +128 -0
- package/dist/lzma1/lz-encoder.d.ts +87 -0
- package/dist/lzma1/lz-encoder.js +166 -0
- package/dist/lzma1/lzma-coder.d.ts +107 -0
- package/dist/lzma1/lzma-coder.js +162 -0
- package/dist/lzma1/lzma-decoder.d.ts +36 -0
- package/dist/lzma1/lzma-decoder.js +484 -0
- package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
- package/dist/lzma1/lzma-encoder-fast.js +114 -0
- package/dist/lzma1/lzma-encoder.d.ts +105 -0
- package/dist/lzma1/lzma-encoder.js +497 -0
- package/dist/lzma1/lzma2-decoder.d.ts +85 -0
- package/dist/lzma1/lzma2-decoder.js +249 -0
- package/dist/lzma1/range-coder.d.ts +18 -0
- package/dist/lzma1/range-coder.js +23 -0
- package/dist/lzma1/range-decoder.d.ts +41 -0
- package/dist/lzma1/range-decoder.js +78 -0
- package/dist/lzma1/range-encoder.d.ts +53 -0
- package/dist/lzma1/range-encoder.js +269 -0
- package/dist/lzma1-stream.d.ts +60 -0
- package/dist/lzma1-stream.js +202 -0
- package/dist/model.d.ts +141 -0
- package/dist/model.js +16 -0
- package/dist/names.d.ts +13 -0
- package/dist/names.js +66 -0
- package/dist/pack.d.ts +72 -0
- package/dist/pack.js +514 -0
- package/dist/plugins.d.ts +13 -0
- package/dist/plugins.js +55 -0
- package/dist/rules.d.ts +25 -0
- package/dist/rules.js +403 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +58 -0
- package/dist/seven.d.ts +75 -0
- package/dist/seven.js +492 -0
- package/dist/sevenzip.d.ts +133 -0
- package/dist/sevenzip.js +529 -0
- package/dist/single.d.ts +103 -0
- package/dist/single.js +453 -0
- package/dist/sniff.d.ts +20 -0
- package/dist/sniff.js +224 -0
- package/dist/stream-meter.d.ts +39 -0
- package/dist/stream-meter.js +54 -0
- package/dist/tar.d.ts +102 -0
- package/dist/tar.js +765 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.js +133 -0
- package/dist/unpack.d.ts +67 -0
- package/dist/unpack.js +194 -0
- package/dist/xz.d.ts +25 -0
- package/dist/xz.js +333 -0
- package/dist/zip-lzma.d.ts +15 -0
- package/dist/zip-lzma.js +137 -0
- package/dist/zip-writer.d.ts +41 -0
- package/dist/zip-writer.js +263 -0
- package/dist/zip.d.ts +99 -0
- package/dist/zip.js +901 -0
- package/dist/zipcrypt.d.ts +47 -0
- package/dist/zipcrypt.js +233 -0
- package/dist/zstd.d.ts +25 -0
- package/dist/zstd.js +160 -0
- package/package.json +94 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** The three keys of PKWARE's traditional encryption. */
|
|
2
|
+
export declare class ZipCryptoKeys {
|
|
3
|
+
private k0;
|
|
4
|
+
private k1;
|
|
5
|
+
private k2;
|
|
6
|
+
constructor(password: Uint8Array);
|
|
7
|
+
private update;
|
|
8
|
+
/** Decrypts `data` into a new buffer, moving the keys along. */
|
|
9
|
+
decrypt(data: Uint8Array): Uint8Array;
|
|
10
|
+
/** Encrypts `data` into a new buffer, moving the keys along. */
|
|
11
|
+
encrypt(data: Uint8Array): Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
/** True when the 12-byte header decrypts with `password` to end in `check`, the byte a writer puts there. */
|
|
14
|
+
export declare function zipCryptoOpens(header: Uint8Array, password: Uint8Array, check: number): boolean;
|
|
15
|
+
/** The stored bytes of a traditionally encrypted entry, decrypted, without the 12-byte header. */
|
|
16
|
+
export declare function zipCryptoDecrypt(chunks: AsyncIterable<Uint8Array>, password: Uint8Array): AsyncGenerator<Uint8Array>;
|
|
17
|
+
/** Salt length by AES strength, as WinZip's AE-1 and AE-2 define them. */
|
|
18
|
+
export declare const AES_SALT: Record<number, number>;
|
|
19
|
+
export declare const aesBits: (strength: number) => 128 | 192 | 256 | undefined;
|
|
20
|
+
/** PBKDF2 with HMAC-SHA1, as RFC 8018 defines it. */
|
|
21
|
+
export declare function pbkdf2Sha1(password: Uint8Array, salt: Uint8Array, iterations: number, length: number): Buffer;
|
|
22
|
+
export interface AesKeys {
|
|
23
|
+
key: Buffer;
|
|
24
|
+
hmacKey: Buffer;
|
|
25
|
+
verifier: Buffer;
|
|
26
|
+
}
|
|
27
|
+
/** WinZip's keys from a password and salt: 1,000 rounds of PBKDF2 with HMAC-SHA1. */
|
|
28
|
+
export declare function aesKeys(password: Uint8Array, salt: Uint8Array, bits: 128 | 192 | 256): AesKeys;
|
|
29
|
+
/** AES in counter mode with WinZip's counter, a 128-bit little-endian number that starts at 1, built on AES-ECB. */
|
|
30
|
+
export declare class AesCtr {
|
|
31
|
+
private readonly counter;
|
|
32
|
+
private pad;
|
|
33
|
+
private readonly ecb;
|
|
34
|
+
constructor(key: Buffer);
|
|
35
|
+
private nextBlocks;
|
|
36
|
+
/** XORs `data` with the key stream, which is how this mode both encrypts and decrypts. */
|
|
37
|
+
apply(data: Uint8Array): Buffer;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The encrypted data of an AES entry, already without its salt, verifier and authentication code, decrypted. The
|
|
41
|
+
* HMAC-SHA1 of the encrypted bytes must match the first ten bytes of `mac`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function aesDecrypt(chunks: AsyncIterable<Uint8Array>, keys: AesKeys, mac: Uint8Array): AsyncGenerator<Uint8Array>;
|
|
44
|
+
/** True when the HMAC-SHA1 of the encrypted bytes matches `mac`, without decrypting them. */
|
|
45
|
+
export declare function aesAuthentic(chunks: AsyncIterable<Uint8Array>, hmacKey: Buffer, mac: Uint8Array): Promise<boolean>;
|
|
46
|
+
/** The ways a password's text can become the bytes a writer used: UTF-8, and Latin-1 when that differs. */
|
|
47
|
+
export declare function passwordBytes(password: string): Uint8Array[];
|
package/dist/zipcrypt.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AesCtr = exports.aesBits = exports.AES_SALT = exports.ZipCryptoKeys = void 0;
|
|
37
|
+
exports.zipCryptoOpens = zipCryptoOpens;
|
|
38
|
+
exports.zipCryptoDecrypt = zipCryptoDecrypt;
|
|
39
|
+
exports.pbkdf2Sha1 = pbkdf2Sha1;
|
|
40
|
+
exports.aesKeys = aesKeys;
|
|
41
|
+
exports.aesDecrypt = aesDecrypt;
|
|
42
|
+
exports.aesAuthentic = aesAuthentic;
|
|
43
|
+
exports.passwordBytes = passwordBytes;
|
|
44
|
+
// The two ways a ZIP entry is commonly encrypted: PKWARE's traditional stream cipher, which is our own code, and
|
|
45
|
+
// WinZip's AES, built only from what OpenSSL's default provider keeps: AES-ECB for the counter mode and HMAC-SHA1
|
|
46
|
+
// for the key derivation and the authentication code.
|
|
47
|
+
const crypto = __importStar(require("node:crypto"));
|
|
48
|
+
const errors_1 = require("./errors");
|
|
49
|
+
const CRC = new Uint32Array(256);
|
|
50
|
+
for (let n = 0; n < 256; n++) {
|
|
51
|
+
let c = n;
|
|
52
|
+
for (let k = 0; k < 8; k++)
|
|
53
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
54
|
+
CRC[n] = c >>> 0;
|
|
55
|
+
}
|
|
56
|
+
const crcByte = (crc, b) => (CRC[(crc ^ b) & 0xff] ^ (crc >>> 8)) >>> 0;
|
|
57
|
+
/** The three keys of PKWARE's traditional encryption. */
|
|
58
|
+
class ZipCryptoKeys {
|
|
59
|
+
k0 = 0x12345678;
|
|
60
|
+
k1 = 0x23456789;
|
|
61
|
+
k2 = 0x34567890;
|
|
62
|
+
constructor(password) {
|
|
63
|
+
for (const b of password)
|
|
64
|
+
this.update(b);
|
|
65
|
+
}
|
|
66
|
+
update(b) {
|
|
67
|
+
this.k0 = crcByte(this.k0, b);
|
|
68
|
+
this.k1 = (Math.imul((this.k1 + (this.k0 & 0xff)) >>> 0, 134775813) + 1) >>> 0;
|
|
69
|
+
this.k2 = crcByte(this.k2, this.k1 >>> 24);
|
|
70
|
+
}
|
|
71
|
+
/** Decrypts `data` into a new buffer, moving the keys along. */
|
|
72
|
+
decrypt(data) {
|
|
73
|
+
const out = new Uint8Array(data.length);
|
|
74
|
+
for (let i = 0; i < data.length; i++) {
|
|
75
|
+
const t = (this.k2 | 2) & 0xffff;
|
|
76
|
+
const p = data[i] ^ ((Math.imul(t, t ^ 1) >>> 8) & 0xff);
|
|
77
|
+
out[i] = p;
|
|
78
|
+
this.update(p);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** Encrypts `data` into a new buffer, moving the keys along. */
|
|
83
|
+
encrypt(data) {
|
|
84
|
+
const out = new Uint8Array(data.length);
|
|
85
|
+
for (let i = 0; i < data.length; i++) {
|
|
86
|
+
const t = (this.k2 | 2) & 0xffff;
|
|
87
|
+
out[i] = data[i] ^ ((Math.imul(t, t ^ 1) >>> 8) & 0xff);
|
|
88
|
+
this.update(data[i]);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.ZipCryptoKeys = ZipCryptoKeys;
|
|
94
|
+
/** True when the 12-byte header decrypts with `password` to end in `check`, the byte a writer puts there. */
|
|
95
|
+
function zipCryptoOpens(header, password, check) {
|
|
96
|
+
return new ZipCryptoKeys(password).decrypt(header)[11] === check;
|
|
97
|
+
}
|
|
98
|
+
/** The stored bytes of a traditionally encrypted entry, decrypted, without the 12-byte header. */
|
|
99
|
+
async function* zipCryptoDecrypt(chunks, password) {
|
|
100
|
+
const keys = new ZipCryptoKeys(password);
|
|
101
|
+
let header = 12;
|
|
102
|
+
for await (const c of chunks) {
|
|
103
|
+
const plain = keys.decrypt(c);
|
|
104
|
+
if (header >= plain.length) {
|
|
105
|
+
header -= plain.length;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
yield plain.subarray(header);
|
|
109
|
+
header = 0;
|
|
110
|
+
}
|
|
111
|
+
if (header)
|
|
112
|
+
throw new errors_1.DataError('An encrypted entry is shorter than its encryption header', true);
|
|
113
|
+
}
|
|
114
|
+
/** Salt length by AES strength, as WinZip's AE-1 and AE-2 define them. */
|
|
115
|
+
exports.AES_SALT = { 1: 8, 2: 12, 3: 16 };
|
|
116
|
+
const AES_BITS = { 1: 128, 2: 192, 3: 256 };
|
|
117
|
+
/** The only cipher names this module hands to OpenSSL, by key size. */
|
|
118
|
+
const AES_ECB = { 128: 'aes-128-ecb', 192: 'aes-192-ecb', 256: 'aes-256-ecb' };
|
|
119
|
+
const aesBits = (strength) => AES_BITS[strength];
|
|
120
|
+
exports.aesBits = aesBits;
|
|
121
|
+
/**
|
|
122
|
+
* HMAC-SHA1 with the key's two padded blocks hashed once, as RFC 2104 defines it. Each message then costs two copies
|
|
123
|
+
* of the started hashes instead of a new HMAC that hashes the key again.
|
|
124
|
+
*/
|
|
125
|
+
class HmacSha1 {
|
|
126
|
+
inner;
|
|
127
|
+
outer;
|
|
128
|
+
constructor(key) {
|
|
129
|
+
const k = Buffer.alloc(64);
|
|
130
|
+
(key.length > 64 ? crypto.createHash('sha1').update(key).digest() : Buffer.from(key)).copy(k);
|
|
131
|
+
this.inner = crypto.createHash('sha1').update(Buffer.from(k.map(b => b ^ 0x36)));
|
|
132
|
+
this.outer = crypto.createHash('sha1').update(Buffer.from(k.map(b => b ^ 0x5c)));
|
|
133
|
+
}
|
|
134
|
+
digest(...parts) {
|
|
135
|
+
const inner = this.inner.copy();
|
|
136
|
+
for (const p of parts)
|
|
137
|
+
inner.update(p);
|
|
138
|
+
return this.outer.copy().update(inner.digest()).digest();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** PBKDF2 with HMAC-SHA1, as RFC 8018 defines it. */
|
|
142
|
+
function pbkdf2Sha1(password, salt, iterations, length) {
|
|
143
|
+
const hmac = new HmacSha1(password);
|
|
144
|
+
const blocks = Math.ceil(length / 20);
|
|
145
|
+
const out = Buffer.alloc(blocks * 20);
|
|
146
|
+
for (let i = 1; i <= blocks; i++) {
|
|
147
|
+
const index = Buffer.alloc(4);
|
|
148
|
+
index.writeUInt32BE(i);
|
|
149
|
+
let u = hmac.digest(salt, index);
|
|
150
|
+
const t = Buffer.from(u);
|
|
151
|
+
for (let j = 1; j < iterations; j++) {
|
|
152
|
+
u = hmac.digest(u);
|
|
153
|
+
for (let k = 0; k < 20; k++)
|
|
154
|
+
t[k] ^= u[k];
|
|
155
|
+
}
|
|
156
|
+
t.copy(out, (i - 1) * 20);
|
|
157
|
+
}
|
|
158
|
+
return out.subarray(0, length);
|
|
159
|
+
}
|
|
160
|
+
/** WinZip's keys from a password and salt: 1,000 rounds of PBKDF2 with HMAC-SHA1. */
|
|
161
|
+
function aesKeys(password, salt, bits) {
|
|
162
|
+
const n = bits / 8;
|
|
163
|
+
const k = pbkdf2Sha1(password, salt, 1000, n * 2 + 2);
|
|
164
|
+
return { key: k.subarray(0, n), hmacKey: k.subarray(n, n * 2), verifier: k.subarray(n * 2) };
|
|
165
|
+
}
|
|
166
|
+
/** AES in counter mode with WinZip's counter, a 128-bit little-endian number that starts at 1, built on AES-ECB. */
|
|
167
|
+
class AesCtr {
|
|
168
|
+
counter = Buffer.alloc(16);
|
|
169
|
+
pad = Buffer.alloc(0);
|
|
170
|
+
ecb;
|
|
171
|
+
constructor(key) {
|
|
172
|
+
const name = AES_ECB[key.length * 8];
|
|
173
|
+
if (name === undefined)
|
|
174
|
+
throw new Error(`No AES cipher for a ${key.length}-byte key`);
|
|
175
|
+
this.ecb = crypto.createCipheriv(name, key, null);
|
|
176
|
+
this.ecb.setAutoPadding(false);
|
|
177
|
+
}
|
|
178
|
+
nextBlocks(count) {
|
|
179
|
+
const blocks = Buffer.alloc(count * 16);
|
|
180
|
+
for (let b = 0; b < count; b++) {
|
|
181
|
+
for (let i = 0; i < 16; i++) {
|
|
182
|
+
this.counter[i] = (this.counter[i] + 1) & 0xff;
|
|
183
|
+
if (this.counter[i] !== 0)
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
this.counter.copy(blocks, b * 16);
|
|
187
|
+
}
|
|
188
|
+
return this.ecb.update(blocks);
|
|
189
|
+
}
|
|
190
|
+
/** XORs `data` with the key stream, which is how this mode both encrypts and decrypts. */
|
|
191
|
+
apply(data) {
|
|
192
|
+
if (this.pad.length < data.length)
|
|
193
|
+
this.pad = Buffer.concat([this.pad, this.nextBlocks(Math.ceil((data.length - this.pad.length) / 16))]);
|
|
194
|
+
const out = Buffer.alloc(data.length);
|
|
195
|
+
for (let i = 0; i < data.length; i++)
|
|
196
|
+
out[i] = data[i] ^ this.pad[i];
|
|
197
|
+
this.pad = this.pad.subarray(data.length);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
exports.AesCtr = AesCtr;
|
|
202
|
+
/**
|
|
203
|
+
* The encrypted data of an AES entry, already without its salt, verifier and authentication code, decrypted. The
|
|
204
|
+
* HMAC-SHA1 of the encrypted bytes must match the first ten bytes of `mac`.
|
|
205
|
+
*/
|
|
206
|
+
async function* aesDecrypt(chunks, keys, mac) {
|
|
207
|
+
const ctr = new AesCtr(keys.key);
|
|
208
|
+
const hmac = crypto.createHmac('sha1', keys.hmacKey);
|
|
209
|
+
for await (const c of chunks) {
|
|
210
|
+
hmac.update(c);
|
|
211
|
+
yield ctr.apply(c);
|
|
212
|
+
}
|
|
213
|
+
if (Buffer.compare(hmac.digest().subarray(0, 10), Buffer.from(mac)) !== 0)
|
|
214
|
+
throw new errors_1.DataError('An AES entry does not match its authentication code, so its data or the password is wrong');
|
|
215
|
+
}
|
|
216
|
+
/** True when the HMAC-SHA1 of the encrypted bytes matches `mac`, without decrypting them. */
|
|
217
|
+
async function aesAuthentic(chunks, hmacKey, mac) {
|
|
218
|
+
const hmac = crypto.createHmac('sha1', hmacKey);
|
|
219
|
+
for await (const c of chunks)
|
|
220
|
+
hmac.update(c);
|
|
221
|
+
return Buffer.compare(hmac.digest().subarray(0, 10), Buffer.from(mac)) === 0;
|
|
222
|
+
}
|
|
223
|
+
/** The ways a password's text can become the bytes a writer used: UTF-8, and Latin-1 when that differs. */
|
|
224
|
+
function passwordBytes(password) {
|
|
225
|
+
const utf8 = Buffer.from(password, 'utf8');
|
|
226
|
+
const out = [utf8];
|
|
227
|
+
if ([...password].every(ch => ch.charCodeAt(0) < 0x100)) {
|
|
228
|
+
const latin1 = Buffer.from(password, 'latin1');
|
|
229
|
+
if (!latin1.equals(utf8))
|
|
230
|
+
out.push(latin1);
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
package/dist/zstd.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ZstdFrame {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
/** A skippable frame, whose content every decoder ignores. */
|
|
5
|
+
skippable: boolean;
|
|
6
|
+
/** Decompressed size, when the frame header gives it. */
|
|
7
|
+
contentSize?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ZstdLayout {
|
|
10
|
+
frames: ZstdFrame[];
|
|
11
|
+
/** Bytes after the last frame that start no frame. */
|
|
12
|
+
trailing: number;
|
|
13
|
+
trailingZeros: boolean;
|
|
14
|
+
}
|
|
15
|
+
type Read = (offset: number, length: number) => Promise<Uint8Array>;
|
|
16
|
+
export declare const isZstdMagic: (b: Uint8Array) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Walks the frames in [start, end). With `single`, the data must be one frame or more and nothing else, as in a
|
|
19
|
+
* ZIP entry, and anything that is not a frame is an error. Otherwise the walk stops at the first bytes that are not a
|
|
20
|
+
* frame, which count as trailing data.
|
|
21
|
+
*/
|
|
22
|
+
export declare function zstdLayout(read: Read, start: number, end: number, single: boolean, check: () => void): Promise<ZstdLayout>;
|
|
23
|
+
/** Compresses `chunks` as one zstd frame with a content checksum. */
|
|
24
|
+
export declare function zstdEncode(chunks: AsyncIterable<Uint8Array>): AsyncGenerator<Uint8Array>;
|
|
25
|
+
export {};
|
package/dist/zstd.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isZstdMagic = void 0;
|
|
37
|
+
exports.zstdLayout = zstdLayout;
|
|
38
|
+
exports.zstdEncode = zstdEncode;
|
|
39
|
+
// zstd frames. The frame and block headers are read here, so the size of every frame is known without decoding it.
|
|
40
|
+
// Decoding is Node's, and a Node without zstd reads none.
|
|
41
|
+
const zlib = __importStar(require("node:zlib"));
|
|
42
|
+
const codecs_1 = require("./codecs");
|
|
43
|
+
const errors_1 = require("./errors");
|
|
44
|
+
const MAGIC = 0xfd2fb528;
|
|
45
|
+
const MAX_BLOCK = 128 * 1024;
|
|
46
|
+
/** zstd decoders refuse a larger window unless told to allow it, and so does Node's. */
|
|
47
|
+
const MAX_WINDOW = 2 ** 27;
|
|
48
|
+
const u32 = (b, at) => (b[at] | (b[at + 1] << 8) | (b[at + 2] << 16) | (b[at + 3] << 24)) >>> 0;
|
|
49
|
+
const isZstdMagic = (b) => b.length >= 4 && (u32(b, 0) === MAGIC || (u32(b, 0) & 0xfffffff0) === 0x184d2a50);
|
|
50
|
+
exports.isZstdMagic = isZstdMagic;
|
|
51
|
+
/**
|
|
52
|
+
* Walks the frames in [start, end). With `single`, the data must be one frame or more and nothing else, as in a
|
|
53
|
+
* ZIP entry, and anything that is not a frame is an error. Otherwise the walk stops at the first bytes that are not a
|
|
54
|
+
* frame, which count as trailing data.
|
|
55
|
+
*/
|
|
56
|
+
async function zstdLayout(read, start, end, single, check) {
|
|
57
|
+
const frames = [];
|
|
58
|
+
let pos = start;
|
|
59
|
+
while (pos < end) {
|
|
60
|
+
check();
|
|
61
|
+
const head = await read(pos, Math.min(18, end - pos));
|
|
62
|
+
if (head.length < 4 || !(0, exports.isZstdMagic)(head)) {
|
|
63
|
+
if (single || frames.length === 0)
|
|
64
|
+
throw new errors_1.DataError('Data that is not a zstd frame');
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (u32(head, 0) !== MAGIC) {
|
|
68
|
+
if (head.length < 8)
|
|
69
|
+
throw new errors_1.DataError('A skippable zstd frame ends early', true);
|
|
70
|
+
const frameEnd = pos + 8 + u32(head, 4);
|
|
71
|
+
if (frameEnd > end)
|
|
72
|
+
throw new errors_1.DataError('A skippable zstd frame ends early', true);
|
|
73
|
+
frames.push({ start: pos, end: frameEnd, skippable: true });
|
|
74
|
+
pos = frameEnd;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const fhd = head[4];
|
|
78
|
+
if (fhd === undefined)
|
|
79
|
+
throw new errors_1.DataError('A zstd frame header ends early', true);
|
|
80
|
+
if (fhd & 0x08)
|
|
81
|
+
throw new errors_1.DataError('A zstd frame header sets its reserved bit');
|
|
82
|
+
const fcsFlag = fhd >> 6;
|
|
83
|
+
const singleSegment = (fhd >> 5) & 1;
|
|
84
|
+
const checksum = (fhd >> 2) & 1;
|
|
85
|
+
const dictFlag = fhd & 3;
|
|
86
|
+
let p = 5;
|
|
87
|
+
let window = 0;
|
|
88
|
+
if (!singleSegment) {
|
|
89
|
+
const wd = head[p++];
|
|
90
|
+
const base = 2 ** (10 + (wd >> 3));
|
|
91
|
+
window = base + (base / 8) * (wd & 7);
|
|
92
|
+
}
|
|
93
|
+
const dictBytes = [0, 1, 2, 4][dictFlag];
|
|
94
|
+
let dictId = 0;
|
|
95
|
+
for (let i = 0; i < dictBytes; i++)
|
|
96
|
+
dictId += head[p + i] * 2 ** (8 * i);
|
|
97
|
+
p += dictBytes;
|
|
98
|
+
const fcsBytes = [singleSegment ? 1 : 0, 2, 4, 8][fcsFlag];
|
|
99
|
+
if (p + fcsBytes > head.length)
|
|
100
|
+
throw new errors_1.DataError('A zstd frame header ends early', true);
|
|
101
|
+
let contentSize;
|
|
102
|
+
if (fcsBytes) {
|
|
103
|
+
contentSize = 0;
|
|
104
|
+
for (let i = 0; i < fcsBytes; i++)
|
|
105
|
+
contentSize += head[p + i] * 2 ** (8 * i);
|
|
106
|
+
if (fcsBytes === 2)
|
|
107
|
+
contentSize += 256;
|
|
108
|
+
}
|
|
109
|
+
p += fcsBytes;
|
|
110
|
+
if (singleSegment)
|
|
111
|
+
window = contentSize ?? 0;
|
|
112
|
+
if (dictId !== 0)
|
|
113
|
+
throw new errors_1.UnsupportedError('A zstd frame needs a dictionary');
|
|
114
|
+
if (window > MAX_WINDOW)
|
|
115
|
+
throw new errors_1.UnsupportedError(`A zstd frame needs a ${window}-byte window, more than decoders allow by default`);
|
|
116
|
+
const blockMax = Math.min(MAX_BLOCK, Math.max(window, 1));
|
|
117
|
+
let at = pos + p;
|
|
118
|
+
for (;;) {
|
|
119
|
+
check();
|
|
120
|
+
const bh = await read(at, 3);
|
|
121
|
+
if (bh.length < 3)
|
|
122
|
+
throw new errors_1.DataError('A zstd frame ends early', true);
|
|
123
|
+
const h = bh[0] | (bh[1] << 8) | (bh[2] << 16);
|
|
124
|
+
const type = (h >> 1) & 3;
|
|
125
|
+
const size = h >> 3;
|
|
126
|
+
if (type === 3)
|
|
127
|
+
throw new errors_1.DataError('A zstd block of the reserved type');
|
|
128
|
+
if (type !== 1 && size > blockMax)
|
|
129
|
+
throw new errors_1.DataError('A zstd block is larger than its frame allows');
|
|
130
|
+
at += 3 + (type === 1 ? 1 : size);
|
|
131
|
+
if (at > end)
|
|
132
|
+
throw new errors_1.DataError('A zstd frame ends early', true);
|
|
133
|
+
if (h & 1)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (checksum)
|
|
137
|
+
at += 4;
|
|
138
|
+
if (at > end)
|
|
139
|
+
throw new errors_1.DataError('A zstd frame ends early', true);
|
|
140
|
+
frames.push({ start: pos, end: at, skippable: false, contentSize });
|
|
141
|
+
pos = at;
|
|
142
|
+
}
|
|
143
|
+
let trailing = 0;
|
|
144
|
+
let zeros = true;
|
|
145
|
+
for (let at = pos; at < end; at += 65536) {
|
|
146
|
+
const b = await read(at, Math.min(65536, end - at));
|
|
147
|
+
trailing += b.length;
|
|
148
|
+
if (zeros && b.some(x => x !== 0))
|
|
149
|
+
zeros = false;
|
|
150
|
+
}
|
|
151
|
+
return { frames, trailing, trailingZeros: zeros };
|
|
152
|
+
}
|
|
153
|
+
/** Compresses `chunks` as one zstd frame with a content checksum. */
|
|
154
|
+
function zstdEncode(chunks) {
|
|
155
|
+
const make = codecs_1.zstdCompressor;
|
|
156
|
+
const checksumFlag = zlib.constants.ZSTD_c_checksumFlag;
|
|
157
|
+
if (!make || checksumFlag === undefined)
|
|
158
|
+
throw new errors_1.UnsupportedError('This Node has no zstd');
|
|
159
|
+
return (0, codecs_1.through)(make({ params: { [checksumFlag]: 1 } }), chunks);
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,96 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patrickdk77/archive-defuse",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sanitize zip, tar, gzip, bzip2, zstd, Brotli, cab, 7z, xz and lzma archives. Detects path traversal, links out of the tree, device files, duplicate names, overlapping entries, hidden and prepended data, encrypted entries and compression bombs, hands every entry to file plugins, scores the risk, and writes a defused archive in the same format with no runtime dependencies.",
|
|
5
|
+
"license": "MIT AND Apache-2.0",
|
|
6
|
+
"author": "patrickdk77",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"archive-defuse": "dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('build-test',{recursive:true,force:true})\"",
|
|
32
|
+
"build": "tsc -p tsconfig.json && node scripts/write-esm-wrapper.js && node scripts/copy-notices.js",
|
|
33
|
+
"build:test": "tsc -p tsconfig.test.json",
|
|
34
|
+
"lint": "biome check .",
|
|
35
|
+
"format": "biome format --write .",
|
|
36
|
+
"test": "npm run lint && npm run build && npm run build:test && mocha --timeout 120000 \"build-test/test/**/*.test.js\"",
|
|
37
|
+
"prepare": "husky || true",
|
|
38
|
+
"prepublishOnly": "npm run clean && npm test",
|
|
39
|
+
"fixtures": "node scripts/make-fixtures.js && node scripts/make-lzma-fixtures.js && node scripts/make-deflate64-fixtures.js"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/patrickdk77/archive-defuse.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/patrickdk77/archive-defuse/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/patrickdk77/archive-defuse",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"zip",
|
|
54
|
+
"tar",
|
|
55
|
+
"gzip",
|
|
56
|
+
"bzip2",
|
|
57
|
+
"zstd",
|
|
58
|
+
"brotli",
|
|
59
|
+
"cab",
|
|
60
|
+
"archive",
|
|
61
|
+
"sanitize",
|
|
62
|
+
"sanitise",
|
|
63
|
+
"scrub",
|
|
64
|
+
"disarm",
|
|
65
|
+
"defuse",
|
|
66
|
+
"cdr",
|
|
67
|
+
"zip-slip",
|
|
68
|
+
"path-traversal",
|
|
69
|
+
"zip-bomb",
|
|
70
|
+
"security",
|
|
71
|
+
"inspect",
|
|
72
|
+
"scanner",
|
|
73
|
+
"detect",
|
|
74
|
+
"7z",
|
|
75
|
+
"7zip",
|
|
76
|
+
"xz",
|
|
77
|
+
"lzma",
|
|
78
|
+
"lzma2",
|
|
79
|
+
"txz",
|
|
80
|
+
"deflate64"
|
|
81
|
+
],
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@biomejs/biome": "^2.5.15",
|
|
84
|
+
"@types/chai": "^5.2.3",
|
|
85
|
+
"@types/mocha": "^10.0.10",
|
|
86
|
+
"@types/node": "^18.19.130",
|
|
87
|
+
"chai": "^6.3.0",
|
|
88
|
+
"husky": "^9.1.7",
|
|
89
|
+
"lint-staged": "^17.6.0",
|
|
90
|
+
"mocha": "^12.0.3",
|
|
91
|
+
"typescript": "^7.0.2"
|
|
92
|
+
},
|
|
93
|
+
"lint-staged": {
|
|
94
|
+
"*.{ts,js,mjs,cjs,json,jsonc}": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
|
|
95
|
+
}
|
|
96
|
+
}
|