mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-14 01:55:58 -07:00
fix(dictionary): stop large character dictionaries from timing out (#189)
This commit is contained in:
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { buildDictionaryZip } from './zip';
|
||||
import { buildDictionaryZip, readDictionaryZipRevision } from './zip';
|
||||
import type { CharacterDictionaryTermEntry } from './types';
|
||||
|
||||
function makeTempDir(): string {
|
||||
@@ -105,3 +105,63 @@ test('buildDictionaryZip writes a valid stored zip without fs.writeFileSync', ()
|
||||
cleanupDir(tempDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('readDictionaryZipRevision reads the built revision and rejects foreign archives', () => {
|
||||
const dir = makeTempDir();
|
||||
try {
|
||||
const zipPath = path.join(dir, 'merged.zip');
|
||||
buildDictionaryZip(
|
||||
zipPath,
|
||||
'SubMiner Character Dictionary',
|
||||
'Character names',
|
||||
'rev-42',
|
||||
[
|
||||
{
|
||||
term: 'ルフィ',
|
||||
reading: 'ルフィ',
|
||||
role: 'main',
|
||||
glossary: [],
|
||||
} as unknown as CharacterDictionaryTermEntry,
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
assert.equal(readDictionaryZipRevision(zipPath), 'rev-42');
|
||||
assert.equal(readDictionaryZipRevision(path.join(dir, 'missing.zip')), null);
|
||||
|
||||
const archive = fs.readFileSync(zipPath);
|
||||
const truncatedPath = path.join(dir, 'truncated.zip');
|
||||
fs.writeFileSync(truncatedPath, archive.subarray(0, 40));
|
||||
assert.equal(readDictionaryZipRevision(truncatedPath), null);
|
||||
|
||||
// An archive cut short after index.json still holds a readable revision, but importing it
|
||||
// would hand Yomitan a half-written file: the missing end-of-central-directory record has to
|
||||
// reject it. One byte off the end is enough to make the record incomplete.
|
||||
for (const missingBytes of [1, 22, archive.length - 200]) {
|
||||
const cutPath = path.join(dir, `cut-${missingBytes}.zip`);
|
||||
fs.writeFileSync(cutPath, archive.subarray(0, archive.length - missingBytes));
|
||||
assert.equal(readDictionaryZipRevision(cutPath), null, `cut of ${missingBytes} bytes`);
|
||||
}
|
||||
|
||||
// Same size, corrupt directory: a record overwritten in place has to be rejected too.
|
||||
const centralStart = archive.readUInt32LE(archive.length - 22 + 16);
|
||||
const brokenSignaturePath = path.join(dir, 'broken-signature.zip');
|
||||
const brokenSignature = Buffer.from(archive);
|
||||
brokenSignature.writeUInt32LE(0xdeadbeef, centralStart);
|
||||
fs.writeFileSync(brokenSignaturePath, brokenSignature);
|
||||
assert.equal(readDictionaryZipRevision(brokenSignaturePath), null);
|
||||
|
||||
const brokenLengthPath = path.join(dir, 'broken-length.zip');
|
||||
const brokenLength = Buffer.from(archive);
|
||||
// Name length that runs the walk past the end of the directory.
|
||||
brokenLength.writeUInt16LE(0xffff, centralStart + 28);
|
||||
fs.writeFileSync(brokenLengthPath, brokenLength);
|
||||
assert.equal(readDictionaryZipRevision(brokenLengthPath), null);
|
||||
|
||||
const foreignPath = path.join(dir, 'foreign.zip');
|
||||
fs.writeFileSync(foreignPath, Buffer.from('not a zip at all', 'utf8'));
|
||||
assert.equal(readDictionaryZipRevision(foreignPath), null);
|
||||
} finally {
|
||||
cleanupDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user