Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const test = require('node:test');
|
||||
const JSZip = require('jszip');
|
||||
const { createArchiveWorkerService, detectArchiveSets } = require('../../src/services/archive-worker-service');
|
||||
|
||||
async function zipFile(filename, entries, options = {}) {
|
||||
const zip = new JSZip();
|
||||
for (const entry of entries) zip.file(entry.name, entry.content || '', entry.options || {});
|
||||
fs.mkdirSync(path.dirname(filename), { recursive: true });
|
||||
fs.writeFileSync(filename, await zip.generateAsync({ type: 'nodebuffer', platform: options.platform || 'DOS', compression: options.compression || 'STORE' }));
|
||||
}
|
||||
|
||||
function fixtureStore(payloadPath, components) {
|
||||
return { openForInspection: () => ({ id: 'job', state: 'quarantined', payloadPath, components }) };
|
||||
}
|
||||
|
||||
test('capabilities and multipart detection report missing dependencies and parts truthfully', () => {
|
||||
const worker = createArchiveWorkerService({ stagingStore: fixtureStore('unused', []) });
|
||||
const capabilities = worker.capabilities();
|
||||
assert.equal(capabilities.zip.available, true);
|
||||
assert.equal(capabilities.rar.available, false);
|
||||
assert.equal(capabilities.sevenZip.available, false);
|
||||
const sets = detectArchiveSets([
|
||||
{ relativePath: 'set/game.part1.rar' }, { relativePath: 'set/game.part3.rar' },
|
||||
{ relativePath: 'other/archive.7z.001' }, { relativePath: 'plain.zip' },
|
||||
{ relativePath: 'classic/data.rar' }, { relativePath: 'classic/data.r00' }, { relativePath: 'classic/data.r02' },
|
||||
]);
|
||||
assert.deepEqual(sets.find(set => set.key === 'set/game.rar').missingParts, [2]);
|
||||
assert.equal(sets.find(set => set.key === 'other/archive.7z').multipart, true);
|
||||
assert.deepEqual(sets.find(set => set.key === 'classic/data.rar').missingParts, [3]);
|
||||
assert.equal(sets.find(set => !set.multipart && set.format === 'zip').parts[0].relativePath, 'plain.zip');
|
||||
});
|
||||
|
||||
test('hostile ZIP matrix rejects traversal, links, entry count, depth and expansion limits', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-archive-hostile-'));
|
||||
try {
|
||||
const cases = [
|
||||
{ name: 'traversal.zip', entries: [{ name: '../escape.txt', content: 'x' }], code: 'ARCHIVE_PATH' },
|
||||
{ name: 'absolute.zip', entries: [{ name: '/escape.txt', content: 'x' }], code: 'ARCHIVE_PATH' },
|
||||
{ name: 'link.zip', entries: [{ name: 'link', content: '../target', options: { unixPermissions: 0o120777 } }], platform: 'UNIX', code: 'ARCHIVE_LINK' },
|
||||
];
|
||||
for (const item of cases) {
|
||||
const filename = path.join(root, item.name);
|
||||
await zipFile(filename, item.entries, { platform: item.platform });
|
||||
const worker = createArchiveWorkerService({ stagingStore: fixtureStore(root, [{ relativePath: item.name, size: fs.statSync(filename).size }]) });
|
||||
const inspected = await worker.inspectJob('job', 'alice');
|
||||
assert.equal(inspected.archives[0].state, 'rejected');
|
||||
assert.equal(inspected.archives[0].code, item.code);
|
||||
}
|
||||
|
||||
await zipFile(path.join(root, 'many.zip'), [{ name: 'a', content: 'a' }, { name: 'b', content: 'b' }]);
|
||||
const many = await createArchiveWorkerService({ stagingStore: fixtureStore(root, [{ relativePath: 'many.zip' }]), limits: { maxEntries: 1 } }).inspectJob('job', 'alice');
|
||||
assert.equal(many.archives[0].code, 'ARCHIVE_LIMIT');
|
||||
await zipFile(path.join(root, 'deep.zip'), [{ name: 'a/b/c/file', content: 'x' }]);
|
||||
const deep = await createArchiveWorkerService({ stagingStore: fixtureStore(root, [{ relativePath: 'deep.zip' }]), limits: { maxDepth: 3 } }).inspectJob('job', 'alice');
|
||||
assert.equal(deep.archives[0].code, 'ARCHIVE_LIMIT');
|
||||
await zipFile(path.join(root, 'bomb.zip'), [{ name: 'large.txt', content: 'A'.repeat(10000) }], { compression: 'DEFLATE' });
|
||||
const bomb = await createArchiveWorkerService({ stagingStore: fixtureStore(root, [{ relativePath: 'bomb.zip' }]), limits: { maxExpansionRatio: 2 } }).inspectJob('job', 'alice');
|
||||
assert.equal(bomb.archives[0].code, 'ARCHIVE_LIMIT');
|
||||
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('corrupt and encrypted ZIPs are classified without partial extraction', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-archive-invalid-'));
|
||||
try {
|
||||
fs.writeFileSync(path.join(root, 'corrupt.zip'), 'not-a-zip');
|
||||
await zipFile(path.join(root, 'encrypted.zip'), [{ name: 'file.txt', content: 'secret' }]);
|
||||
const encrypted = fs.readFileSync(path.join(root, 'encrypted.zip'));
|
||||
encrypted.writeUInt16LE(encrypted.readUInt16LE(6) | 1, 6);
|
||||
const central = encrypted.indexOf(Buffer.from([0x50, 0x4b, 0x01, 0x02]));
|
||||
encrypted.writeUInt16LE(encrypted.readUInt16LE(central + 8) | 1, central + 8);
|
||||
fs.writeFileSync(path.join(root, 'encrypted.zip'), encrypted);
|
||||
const worker = createArchiveWorkerService({ stagingStore: fixtureStore(root, [{ relativePath: 'corrupt.zip' }, { relativePath: 'encrypted.zip' }]) });
|
||||
const result = await worker.inspectJob('job', 'alice');
|
||||
assert.equal(result.archives.find(item => item.parts[0].relativePath === 'corrupt.zip').state, 'corrupt');
|
||||
assert.equal(result.archives.find(item => item.parts[0].relativePath === 'encrypted.zip').state, 'encrypted');
|
||||
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('interrupted ZIP extraction resumes verified entries and retains the original archive', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-archive-resume-'));
|
||||
try {
|
||||
const filename = path.join(root, 'bundle.zip');
|
||||
await zipFile(filename, [{ name: 'one.txt', content: 'one' }, { name: 'two.txt', content: 'two' }]);
|
||||
const store = fixtureStore(root, [{ relativePath: 'bundle.zip', size: fs.statSync(filename).size }]);
|
||||
let checks = 0;
|
||||
const interrupted = createArchiveWorkerService({ stagingStore: store, isCancelled: () => ++checks > 1 });
|
||||
await assert.rejects(interrupted.extract({ jobId: 'job', owner: 'alice', relativePath: 'bundle.zip' }), error => error.code === 'ARCHIVE_CANCELLED');
|
||||
const resumed = createArchiveWorkerService({ stagingStore: store });
|
||||
const result = await resumed.extract({ jobId: 'job', owner: 'alice', relativePath: 'bundle.zip' });
|
||||
assert.equal(result.state, 'complete');
|
||||
assert.equal(result.resumedEntries, 1);
|
||||
assert.equal(result.originalRetained, true);
|
||||
assert.equal(fs.readFileSync(filename).length, fs.statSync(filename).size);
|
||||
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
||||
});
|
||||
Reference in new issue
Block a user