mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
97cd03325f
Scanned books are commonly distributed as DjVu, which the scanner only stored as a supplement. Decode pages on the server with DjVuLibre so DjVu imports with a cover and page count, opens in the image reader with its embedded OCR text, and can be the preferred format of an ordinary email recipient. Migration 0113 widens the recipient format check to allow it. Decoding runs on validated private snapshots behind a bounded, reader-first queue, and revalidation and cached pages are answered without a decoder. Released iOS builds cannot open DjVu, so series continuation offers it only to callers sending includeDjvu=true, and the dashboard and notebook read file never name it. The first full scan after upgrading promotes stored .djvu supplements to content.
83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
import { mkdtemp, open, rm, writeFile } from 'fs/promises';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
import { djvuBundleFixture, djvuPageFixture } from '../../../test/e2e/djvu-fixture';
|
|
import { validateDjvuContainer } from './djvu-container';
|
|
|
|
describe('DjVu container containment', () => {
|
|
let directory: string;
|
|
beforeEach(async () => {
|
|
directory = await mkdtemp(join(tmpdir(), 'djvu-container-test-'));
|
|
});
|
|
afterEach(async () => {
|
|
await rm(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
async function validate(buffer: Buffer) {
|
|
const path = join(directory, 'document.djvu');
|
|
await writeFile(path, buffer);
|
|
const handle = await open(path, 'r');
|
|
try {
|
|
return await validateDjvuContainer(handle, buffer.length);
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
}
|
|
|
|
it('accepts standalone and bundled pages', async () => {
|
|
expect(await validate(djvuPageFixture())).toBe(1);
|
|
expect(await validate(djvuBundleFixture())).toBe(2);
|
|
});
|
|
|
|
it('accepts bytes after the top-level form, which DjVuLibre ignores', async () => {
|
|
expect(await validate(Buffer.concat([djvuPageFixture(), Buffer.alloc(3)]))).toBe(1);
|
|
});
|
|
|
|
it('rejects a top-level form that claims more bytes than the file holds', async () => {
|
|
const buffer = djvuPageFixture();
|
|
buffer.writeUInt32BE(buffer.length, 8);
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
});
|
|
|
|
it('rejects indirect document directories', async () => {
|
|
const buffer = djvuBundleFixture();
|
|
buffer[24] = 1;
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
});
|
|
|
|
it('rejects directory offsets into unvalidated payloads', async () => {
|
|
const buffer = djvuBundleFixture();
|
|
buffer.writeUInt32BE(80, 27);
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
});
|
|
|
|
it.each(['../secret', '/path/to/component', 'file:///secret', 'https://example.com/component', 'dictionary'])(
|
|
'rejects standalone references: %s',
|
|
async (name) => {
|
|
const source = djvuPageFixture();
|
|
const ref = Buffer.from(name);
|
|
const chunk = Buffer.alloc(8 + ref.length);
|
|
chunk.write('INCL');
|
|
chunk.writeUInt32BE(ref.length, 4);
|
|
ref.copy(chunk, 8);
|
|
const buffer = Buffer.concat([source, Buffer.alloc(1), chunk]);
|
|
buffer.writeUInt32BE(buffer.length - 12, 8);
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
},
|
|
);
|
|
|
|
it('rejects source dimensions that exceed the decode bound', async () => {
|
|
const buffer = djvuPageFixture();
|
|
buffer.writeUInt16BE(65535, 24);
|
|
buffer.writeUInt16BE(65535, 26);
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
});
|
|
|
|
it('rejects malformed sizes and truncated chunks', async () => {
|
|
await expect(validate(Buffer.from('not djvu'))).rejects.toMatchObject({ status: 422 });
|
|
const buffer = djvuPageFixture().subarray(0, 60);
|
|
buffer.writeUInt32BE(buffer.length - 12, 8);
|
|
await expect(validate(buffer)).rejects.toMatchObject({ status: 422 });
|
|
});
|
|
});
|