Probe 7z listings and ZIP starts
7z keeps its file list at the end of the archive, so the 7z-tail candidates (the last segment of the highest volume) were queued but never probed, and the .7z.001 start only gave the type. The prober now claims 7z-tail candidates after the archives; lib/sevenzip finds the end header in the article, decodes the LZMA-packed header (a TypeScript port of the LZMA SDK's reference decoder) and lists the files with their sizes. An AES-encrypted header means passworded without names. Names feed the inner name and the judge's fake rule. An unsplit .7z of several segments now also gets a tail candidate, and .zip files an archive candidate: their first segment's local headers give the first entries and whether they are encrypted. Both in the Rust edge and the TypeScript indexer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b61215f6b
commit
d446bb57df
25 files changed
+4840
-11
No files matched your search
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+4017
File diff suppressed because it is too large.
Load diff
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Binary file not shown.
@@ -55,6 +55,20 @@ test.group('edge / RangeIndexer', () => {
|
||||
assert.equal(batch.next, 1_000_001)
|
||||
})
|
||||
|
||||
test("archive candidates: a split 7z's start and its highest volume's end, an unsplit 7z's start and end, a ZIP's start", ({ assert }) => {
|
||||
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1_000_000, rules, edgeArticles: 10 })
|
||||
indexer.add([
|
||||
...post(100, 'Split.Seven.7z.001', 2, { counter: 'Split.Seven [1/3]' }),
|
||||
...post(110, 'Split.Seven.7z.003', 2, { counter: 'Split.Seven [3/3]' }),
|
||||
...post(120, 'Split.Seven.7z.002', 2, { counter: 'Split.Seven [2/3]' }),
|
||||
...post(200, 'Whole.Seven.7z', 3, { counter: 'Whole.Seven [1/1]' }),
|
||||
...post(300, 'Some.Zip.zip', 2, { counter: 'Some.Zip [1/1]' }),
|
||||
], 200_000)
|
||||
indexer.finish()
|
||||
const got = indexer.take().candidates.map((c) => `${c.kind} ${c.file}`).sort()
|
||||
assert.deepEqual(got, ['7z-tail Split.Seven.7z.003', '7z-tail Whole.Seven.7z', 'archive Some.Zip.zip', 'archive Split.Seven.7z.001', 'archive Whole.Seven.7z'])
|
||||
})
|
||||
|
||||
test('files the edges cut leave as fragments; one missing parts in the middle is only counted', ({ assert }) => {
|
||||
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1_000_000, rules, edgeArticles: 1000 })
|
||||
indexer.add([
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { crc32 } from 'node:zlib'
|
||||
import yencode from 'yencode'
|
||||
import { crc32Hex } from '#lib/yenc/index'
|
||||
@@ -148,4 +149,45 @@ test.group('probe / innerName', () => {
|
||||
// Nor does packaging: a lone par2 volume.
|
||||
assert.isNull(innerName([{ kind: 'lone', result: { yencName: 'a1b2.vol007+08.par2', size: 1, type: 'par2' } }], 'x9y8'))
|
||||
})
|
||||
|
||||
test("a 7z-tail: the listing from the last segment of the last volume; one that isn't the last segment: none", ({ assert }) => {
|
||||
const tail = readFileSync(new URL('../../fixtures/sevenzip/split-last-volume.tail', import.meta.url))
|
||||
// The volume's last 8,000 bytes as its third part, of a volume 1,000,000 bytes long.
|
||||
const last = tail.subarray(tail.length - 8000)
|
||||
const part = (data: Buffer, begin: number) =>
|
||||
Buffer.from(
|
||||
[`=ybegin part=3 total=3 line=128 size=1000000 name=x.7z.004`, `=ypart begin=${begin} end=${begin + data.length - 1}`, yencode.encode(data).toString('latin1'), `=yend size=${data.length} part=3 pcrc32=${crc32Hex(data)}`].join('\r\n'),
|
||||
'latin1'
|
||||
)
|
||||
const result = inspect(part(last, 1_000_000 - 8000 + 1), '7z-tail')
|
||||
assert.equal(result.entries, 4)
|
||||
assert.deepEqual(result.names?.[0], 'Some.Movie.2020.1080p.BluRay.x264-GRP/some.movie.2020.1080p.bluray.x264-grp.mkv')
|
||||
assert.isFalse(result.passworded)
|
||||
assert.isUndefined(inspect(part(last, 1), '7z-tail').names)
|
||||
const encrypted = readFileSync(new URL('../../fixtures/sevenzip/header-encrypted.tail', import.meta.url))
|
||||
const hidden = inspect(part(encrypted.subarray(encrypted.length - 8000), 1_000_000 - 8000 + 1), '7z-tail')
|
||||
assert.isTrue(hidden.passworded)
|
||||
assert.equal(hidden.entries, 0)
|
||||
assert.isUndefined(hidden.names)
|
||||
// A small 7z whole in one article, probed as an archive start.
|
||||
assert.deepEqual(inspect(article(readFileSync(new URL('../../fixtures/sevenzip/tiny.7z', import.meta.url)), 'x.7z'), 'archive').names, ['b.nfo', 'a.txt'])
|
||||
})
|
||||
|
||||
test('a ZIP start: the names of its first entries, and whether they are encrypted', ({ assert }) => {
|
||||
const local = (name: string, data: Buffer, flags = 0) => {
|
||||
const header = Buffer.alloc(30)
|
||||
header.writeUInt32LE(0x04034b50, 0)
|
||||
header.writeUInt16LE(flags, 6)
|
||||
header.writeUInt32LE(data.length, 18)
|
||||
header.writeUInt32LE(data.length, 22)
|
||||
header.writeUInt16LE(Buffer.byteLength(name), 26)
|
||||
return Buffer.concat([header, Buffer.from(name), data])
|
||||
}
|
||||
const zip = Buffer.concat([local('Album/', Buffer.alloc(0)), local('Album/01 - Track.flac', Buffer.alloc(3000, 1)), local('Album/cover.jpg', Buffer.alloc(500, 2))])
|
||||
const plain = inspect(article(zip, 'album.zip'))
|
||||
assert.equal(plain.type, 'zip')
|
||||
assert.deepEqual(plain.names, ['Album/01 - Track.flac', 'Album/cover.jpg'])
|
||||
assert.isFalse(plain.passworded)
|
||||
assert.isTrue(inspect(article(local('secret.mkv', Buffer.alloc(100), 0x1), 'x.zip')).passworded)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { lzmaDecode, LzmaError } from '#lib/sevenzip/lzma'
|
||||
|
||||
const fixture = (name: string) => readFileSync(new URL(`../../fixtures/sevenzip/${name}`, import.meta.url))
|
||||
|
||||
test.group('probe / LZMA', () => {
|
||||
test('decodes a stream Python made (.lzma: 5 property bytes, the size, the raw stream)', ({ assert }) => {
|
||||
const alone = fixture('sample.lzma')
|
||||
const expected = fixture('sample.bin')
|
||||
const decoded = lzmaDecode(alone.subarray(0, 5), alone.subarray(13), expected.length)
|
||||
assert.isTrue(decoded.equals(expected))
|
||||
})
|
||||
|
||||
test('a cut-off or damaged stream throws LzmaError', ({ assert }) => {
|
||||
const alone = fixture('sample.lzma')
|
||||
const size = fixture('sample.bin').length
|
||||
assert.throws(() => lzmaDecode(alone.subarray(0, 5), alone.subarray(13, 200), size), LzmaError)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { parseSevenZipTail } from '#lib/sevenzip/index'
|
||||
|
||||
const fixture = (name: string) => readFileSync(new URL(`../../fixtures/sevenzip/${name}`, import.meta.url))
|
||||
const DIR = 'Some.Movie.2020.1080p.BluRay.x264-GRP'
|
||||
|
||||
test.group('probe / 7z listing from the archive end', () => {
|
||||
test('an LZMA-packed header (7z default): files with sizes, directories left out', ({ assert }) => {
|
||||
const listing = parseSevenZipTail(fixture('plain.tail'))
|
||||
assert.deepEqual(listing, {
|
||||
files: [
|
||||
{ name: `${DIR}/empty.txt`, size: 0 },
|
||||
{ name: `${DIR}/Sample/sample.mkv`, size: 20000 },
|
||||
{ name: `${DIR}/grp.nfo`, size: 4 },
|
||||
{ name: `${DIR}/some.movie.2020.1080p.bluray.x264-grp.mkv`, size: 300000 },
|
||||
],
|
||||
encrypted: false,
|
||||
headerEncrypted: false,
|
||||
})
|
||||
})
|
||||
|
||||
test('an uncompressed header (7z -mhc=off) and the last volume of a split archive', ({ assert }) => {
|
||||
for (const name of ['uncompressed-header.tail', 'split-last-volume.tail']) {
|
||||
const listing = parseSevenZipTail(fixture(name))!
|
||||
assert.lengthOf(listing.files, 4, name)
|
||||
assert.isFalse(listing.encrypted, name)
|
||||
}
|
||||
})
|
||||
|
||||
test('encrypted entries: names listed, encrypted; an encrypted header (7z -mhe): no names, encrypted', ({ assert }) => {
|
||||
const files = parseSevenZipTail(fixture('files-encrypted.tail'))!
|
||||
assert.lengthOf(files.files, 4)
|
||||
assert.isTrue(files.encrypted)
|
||||
assert.isFalse(files.headerEncrypted)
|
||||
assert.deepEqual(parseSevenZipTail(fixture('header-encrypted.tail')), { files: [], encrypted: true, headerEncrypted: true })
|
||||
})
|
||||
|
||||
test('a whole small archive; not the end of an archive: null', ({ assert }) => {
|
||||
assert.deepEqual(parseSevenZipTail(fixture('tiny.7z'))?.files.map((f) => f.name), ['a.txt', 'b.nfo'])
|
||||
// The end cut off, or data that isn't 7z at all.
|
||||
assert.isNull(parseSevenZipTail(fixture('plain.tail').subarray(0, 30000)))
|
||||
assert.isNull(parseSevenZipTail(fixture('sample.bin')))
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user