Something went wrong. Try again.
streaming 7-Zip archive extractor
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291import { assertEquals, assertRejects } from '@std/assert';
import { ReaderError, TruncatedArchiveError } from '../lib/errors.ts';import { concatReaders } from '../lib/reader/concat.ts';import { fromFetch, fromFile, fromUint8Array } from '../lib/reader/common.ts';import { concat, readBytes } from '../lib/utils/buffer.ts';
const sample = new Uint8Array(256);for (let i = 0; i < sample.length; i++) { sample[i] = i;}
Deno.test('fromUint8Array: length matches input', () => { const reader = fromUint8Array(sample); assertEquals(reader.length, 256);});
Deno.test('fromUint8Array: full read returns identical bytes', async () => { const reader = fromUint8Array(sample); const got = await readBytes(reader, 0, sample.length); assertEquals(got, sample);});
Deno.test('fromUint8Array: ranged read returns the requested slice', async () => { const reader = fromUint8Array(sample); const got = await readBytes(reader, 100, 32); assertEquals(got, sample.subarray(100, 132));});
Deno.test('fromUint8Array: read past end throws TruncatedArchiveError', async () => { const reader = fromUint8Array(sample); await assertRejects( () => readBytes(reader, 200, 100), TruncatedArchiveError, );});
Deno.test('fromUint8Array: read with omitted length reads to end', async () => { const reader = fromUint8Array(sample); const stream = await reader.read(250); const chunks: Uint8Array[] = []; for await (const chunk of stream) { chunks.push(chunk); } const merged = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0)); let pos = 0; for (const chunk of chunks) { merged.set(chunk, pos); pos += chunk.length; } assertEquals(merged, sample.subarray(250));});
Deno.test('fromFile: matches fromUint8Array for the same payload', async () => { const blob = new Blob([sample]); const reader = fromFile(blob); assertEquals(reader.length, 256); const got = await readBytes(reader, 64, 32); assertEquals(got, sample.subarray(64, 96));});
Deno.test('readBytes: BYOB path used when fromFile delivers a byte stream and size >= 4096', async () => { // fromFile re-emits Blob.stream() through a byte-typed stream, so it still // supports BYOB readers. readBytes prefers BYOB at size>=4096, so this // exercises the branch fromUint8Array can't reach. const big = new Uint8Array(8192); for (let i = 0; i < big.length; i++) { big[i] = (i * 7) & 0xff; } const reader = fromFile(new Blob([big])); const got = await readBytes(reader, 0, big.length); assertEquals(got, big);});
// a Blob whose slice streams deliver their bytes but never emit a final// `{ done: true }` — the Bun behaviour fromFile has to defend against.const neverClosingBlob = (payload: Uint8Array<ArrayBuffer>): Blob => ({ size: payload.byteLength, slice(start = 0, end = payload.byteLength) { const part = payload.subarray(start, end); return { size: part.byteLength, stream: () => new ReadableStream<Uint8Array<ArrayBuffer>>({ start(controller) { if (part.byteLength > 0) { // fresh buffer per chunk, like a real Blob.stream(); a byte // stream detaches whatever it enqueues. controller.enqueue(part.slice() as Uint8Array<ArrayBuffer>); } // deliberately never close() }, }), }; }, }) as unknown as Blob;
// drains a stream to completion, failing fast instead of hanging if it never// closes (the symptom this guards against).const collectBounded = async (stream: ReadableStream<Uint8Array>): Promise<Uint8Array<ArrayBuffer>> => { let timer: ReturnType<typeof setTimeout> | undefined; const timeout = new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error('stream did not close within 5s')), 5000); }); const drain = (async () => { const chunks: Uint8Array[] = []; for await (const chunk of stream) { chunks.push(chunk); } return concat(chunks); })(); try { return await Promise.race([drain, timeout]); } finally { clearTimeout(timer); }};
Deno.test('fromFile: a non-empty range ends even when the source never closes', async () => { const payload = new Uint8Array(1000); for (let i = 0; i < payload.length; i++) { payload[i] = (i * 3) & 0xff; } const reader = fromFile(neverClosingBlob(payload)); const got = await collectBounded(await reader.read(100, 300)); assertEquals(got, payload.subarray(100, 400));});
Deno.test('fromFile: an empty range ends with no bytes', async () => { const payload = new Uint8Array(1000); const reader = fromFile(neverClosingBlob(payload)); const got = await collectBounded(await reader.read(100, 0)); assertEquals(got.byteLength, 0);});
Deno.test('concatReaders: single reader passes through unchanged', () => { const inner = fromUint8Array(sample); const out = concatReaders([inner]); assertEquals(out, inner);});
Deno.test('concatReaders: rejects empty reader list', () => { let threw = false; try { concatReaders([]); } catch { threw = true; } assertEquals(threw, true);});
Deno.test('concatReaders: ranged read spanning a volume boundary', async () => { const left = fromUint8Array(sample.slice(0, 100) as Uint8Array<ArrayBuffer>); const right = fromUint8Array(sample.slice(100) as Uint8Array<ArrayBuffer>); const combined = concatReaders([left, right]); assertEquals(combined.length, 256); const got = await readBytes(combined, 80, 100); // crosses the 100-byte boundary assertEquals(got, sample.subarray(80, 180));});
// #region fromFetch
const serveFixture = ( bytes: Uint8Array<ArrayBuffer>, overrides: { headStatus?: number; headHeaders?: Record<string, string | null>; rangeStatus?: number; rangeBody?: BodyInit | null; } = {},): { url: string; shutdown: () => Promise<void> } => { const ac = new AbortController(); const server = Deno.serve( { hostname: '127.0.0.1', onListen: () => {}, port: 0, signal: ac.signal }, (req) => { if (req.method === 'HEAD') { const headers = new Headers(); const baseLength = overrides.headHeaders?.['content-length']; if (baseLength !== null) { headers.set('content-length', baseLength ?? String(bytes.byteLength)); } return new Response(null, { headers, status: overrides.headStatus ?? 200 }); } if (req.method === 'GET') { const range = req.headers.get('range'); if (range !== null) { const m = /^bytes=(\d+)-(\d+)$/.exec(range); if (m === null) { return new Response('bad range', { status: 416 }); } const start = parseInt(m[1], 10); const end = parseInt(m[2], 10); return new Response( overrides.rangeBody === undefined ? bytes.subarray(start, end + 1) as Uint8Array<ArrayBuffer> : overrides.rangeBody, { status: overrides.rangeStatus ?? 206 }, ); } return new Response(bytes, { status: 200 }); } return new Response(null, { status: 405 }); }, ); return { shutdown: async () => { ac.abort(); await server.finished; }, url: `http://127.0.0.1:${server.addr.port}`, };};
Deno.test('fromFetch: happy path resolves content-length and serves ranges', async () => { const { url, shutdown } = serveFixture(sample); try { const reader = await fromFetch({ input: url }); assertEquals(reader.length, sample.length); const got = await readBytes(reader, 32, 64); assertEquals(got, sample.subarray(32, 96)); } finally { await shutdown(); }});
Deno.test('fromFetch: HEAD non-2xx throws ReaderError', async () => { const { url, shutdown } = serveFixture(sample, { headStatus: 500 }); try { await assertRejects(() => fromFetch({ input: url }), ReaderError); } finally { await shutdown(); }});
Deno.test('fromFetch: missing content-length throws ReaderError', async () => { const { url, shutdown } = serveFixture(sample, { headHeaders: { 'content-length': null } }); try { await assertRejects(() => fromFetch({ input: url }), ReaderError); } finally { await shutdown(); }});
Deno.test('fromFetch: invalid content-length throws ReaderError', async () => { const { url, shutdown } = serveFixture(sample, { headHeaders: { 'content-length': 'nope' } }); try { await assertRejects(() => fromFetch({ input: url }), ReaderError); } finally { await shutdown(); }});
Deno.test('fromFetch: range request returning 200 (no range support) throws ReaderError', async () => { const { url, shutdown } = serveFixture(sample, { rangeStatus: 200 }); try { const reader = await fromFetch({ input: url }); await assertRejects(() => readBytes(reader, 0, 16), ReaderError); } finally { await shutdown(); }});
Deno.test('fromFetch: range request non-success throws ReaderError', async () => { const { url, shutdown } = serveFixture(sample, { rangeStatus: 500 }); try { const reader = await fromFetch({ input: url }); await assertRejects(() => readBytes(reader, 0, 16), ReaderError); } finally { await shutdown(); }});
Deno.test('fromFetch: caller-supplied fetch override is honoured', async () => { let called = 0; const customFetch: typeof fetch = (input, init) => { called++; return fetch(input, init); }; const { url, shutdown } = serveFixture(sample); try { const reader = await fromFetch({ fetch: customFetch, input: url }); await readBytes(reader, 0, 16); assertEquals(called >= 2, true); // HEAD + at least one range GET } finally { await shutdown(); }});
// #endregion