import { assertRejects } from '@std/assert'; import { ChecksumMismatchError, InvalidPasswordError, InvalidSignatureError, MissingPasswordError, TruncatedArchiveError, UnsupportedFeatureError, } from '../lib/errors.ts'; import { unsevenzip } from '../lib/extract.ts'; import { fromUint8Array } from '../lib/reader/common.ts'; const COMMONS_COMPRESS = new URL('./fixtures/commons-compress/', import.meta.url); const LIBARCHIVE = new URL('./fixtures/libarchive/', import.meta.url); const PY7ZR = new URL('./fixtures/py7zr/', import.meta.url); const load = (base: URL, name: string): Uint8Array => Deno.readFileSync(new URL(name, base)); const drain = async (reader: ReturnType, password?: string): Promise => { for await (const entry of unsevenzip(reader, password !== undefined ? { password } : {})) { await entry.bytes(); } }; // #region unsupported codecs Deno.test('unsevenzip: bzip2 throws UnsupportedFeatureError at decode time', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_bzip2.7z')); await assertRejects(() => drain(reader), UnsupportedFeatureError); }); Deno.test('unsevenzip: bcj2+bzip2 throws UnsupportedFeatureError at decode time', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_bcj2_bzip2.7z')); await assertRejects(() => drain(reader), UnsupportedFeatureError); }); Deno.test('unsevenzip: lz4 throws UnsupportedFeatureError at decode time', async () => { const reader = fromUint8Array(load(PY7ZR, 'lz4.7z')); await assertRejects(() => drain(reader), UnsupportedFeatureError); }); Deno.test('unsevenzip: zstd+brotli throws UnsupportedFeatureError at decode time', async () => { const reader = fromUint8Array(load(PY7ZR, 'zstdmt-brotli.7z')); await assertRejects(() => drain(reader), UnsupportedFeatureError); }); // #endregion // #region missing password Deno.test('unsevenzip: encrypted entry without password throws MissingPasswordError', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_encryption.7z')); await assertRejects(() => drain(reader), MissingPasswordError); }); Deno.test('unsevenzip: encrypted header without password throws MissingPasswordError', async () => { // bla.encrypted has an encrypted EncodedHeader — failure surfaces during // container parsing, before any entry is yielded. const reader = fromUint8Array(load(COMMONS_COMPRESS, 'bla.encrypted.7z')); await assertRejects(() => drain(reader), MissingPasswordError); }); Deno.test('unsevenzip: filename_encryption without password throws MissingPasswordError', async () => { const reader = fromUint8Array(load(PY7ZR, 'filename_encryption.7z')); await assertRejects(() => drain(reader), MissingPasswordError); }); // #endregion // #region wrong password Deno.test('unsevenzip: wrong password on encrypted entry throws InvalidPasswordError', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_encryption.7z')); await assertRejects(() => drain(reader, 'wrong'), InvalidPasswordError); }); Deno.test('unsevenzip: wrong password on encrypted header throws InvalidPasswordError', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_encryption_header.7z')); await assertRejects(() => drain(reader, 'wrong'), InvalidPasswordError); }); Deno.test('unsevenzip: wrong password on partially-encrypted archive throws InvalidPasswordError', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_encryption_partially.7z')); await assertRejects(() => drain(reader, 'wrong'), InvalidPasswordError); }); Deno.test('unsevenzip: wrong password on bla.encrypted throws InvalidPasswordError', async () => { const reader = fromUint8Array(load(COMMONS_COMPRESS, 'bla.encrypted.7z')); await assertRejects(() => drain(reader, 'wrong'), InvalidPasswordError); }); Deno.test('unsevenzip: wrong password on filename_encryption throws InvalidPasswordError', async () => { const reader = fromUint8Array(load(PY7ZR, 'filename_encryption.7z')); await assertRejects(() => drain(reader, 'wrong'), InvalidPasswordError); }); // #endregion // #region malformed archives Deno.test('unsevenzip: libarchive malformed.7z truncated NextHeader', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_malformed.7z')); await assertRejects(() => drain(reader), TruncatedArchiveError); }); Deno.test('unsevenzip: libarchive malformed2.7z truncated NextHeader', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_malformed2.7z')); await assertRejects(() => drain(reader), TruncatedArchiveError); }); Deno.test('unsevenzip: libarchive malformed3.7z rejected as non-7z input', async () => { const reader = fromUint8Array(load(LIBARCHIVE, 'test_read_format_7zip_malformed3.7z')); await assertRejects(() => drain(reader), InvalidSignatureError); }); Deno.test('unsevenzip: COMPRESS-542-endheadercorrupted fails StartHeader CRC', async () => { const reader = fromUint8Array(load(COMMONS_COMPRESS, 'COMPRESS-542-endheadercorrupted.7z')); await assertRejects(() => drain(reader), ChecksumMismatchError); }); Deno.test('unsevenzip: COMPRESS-542-endheadercorrupted2 fails StartHeader CRC', async () => { const reader = fromUint8Array(load(COMMONS_COMPRESS, 'COMPRESS-542-endheadercorrupted2.7z')); await assertRejects(() => drain(reader), ChecksumMismatchError); }); Deno.test('unsevenzip: bla.noendheaderoffset fails StartHeader CRC', async () => { const reader = fromUint8Array(load(COMMONS_COMPRESS, 'bla.noendheaderoffset.7z')); await assertRejects(() => drain(reader), ChecksumMismatchError); }); // #endregion // #region CRC mismatch on stored content Deno.test('unsevenzip: mangled pack byte triggers ChecksumMismatchError on extraction', async () => { // 7z-hello-mhc-off-copy.7z stores raw "Hello, world!\n" at offset 32 with // an entry CRC. flipping one byte changes the computed CRC over the // reconstructed plaintext, so one of the CRC checks (folder- or entry- // level) must fire. const data = load(COMMONS_COMPRESS, '7z-hello-mhc-off-copy.7z'); const mangled = new Uint8Array(data); mangled[32] ^= 0xff; const reader = fromUint8Array(mangled); await assertRejects(() => drain(reader), ChecksumMismatchError); }); // #endregion