// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { assertEquals, assertRejects } from "@std/assert"; import { toBytes } from "@std/streams/unstable-to-bytes"; import { TarStream, type TarStreamInput } from "./tar_stream.ts"; import { type OldStyleFormat, type PosixUstarFormat, UntarStream, } from "./untar_stream.ts"; Deno.test("expandTarArchiveCheckingHeaders", async () => { const text = new TextEncoder().encode("Hello World!"); const seconds = Math.floor(Date.now() / 1000); const readable = ReadableStream.from([ { type: "directory", path: "./potato", options: { mode: 111111, uid: 12, gid: 21, mtime: seconds, uname: "potato", gname: "cake", devmajor: "ice", devminor: "scream", }, }, { type: "file", path: "./text.txt", size: text.length, readable: ReadableStream.from([text.slice()]), options: { mtime: seconds }, }, ]) .pipeThrough(new TarStream()) .pipeThrough(new UntarStream()); const headers: (OldStyleFormat | PosixUstarFormat)[] = []; for await (const entry of readable) { headers.push(entry.header); await entry.readable?.cancel(); } assertEquals(headers, [{ name: "./potato", mode: 111111, uid: 12, gid: 21, mtime: seconds, uname: "potato", gname: "cake", devmajor: "ice", devminor: "scream", size: 0, typeflag: "5", linkname: "", magic: "ustar\0", version: "00", prefix: "", }, { name: "./text.txt", mode: 644, uid: 0, gid: 0, mtime: seconds, uname: "", gname: "", devmajor: "", devminor: "", size: text.length, typeflag: "0", linkname: "", magic: "ustar\0", version: "00", prefix: "", }]); }); Deno.test("expandTarArchiveCheckingBodies", async () => { const text = new TextEncoder().encode("Hello World!"); const readable = ReadableStream.from([ { type: "directory", path: "./potato", }, { type: "file", path: "./text.txt", size: text.length, readable: ReadableStream.from([text.slice()]), }, ]) .pipeThrough(new TarStream()) .pipeThrough(new UntarStream()); let buffer = new Uint8Array(); for await (const item of readable) { if (item.readable) buffer = await toBytes(item.readable); } assertEquals(buffer, text); }); Deno.test("UntarStream() with size equals to multiple of 512", async () => { const size = 512 * 3; const data = Uint8Array.from( { length: size }, () => Math.floor(Math.random() * 256), ); const readable = ReadableStream.from([ { type: "file", path: "name", size, readable: ReadableStream.from([data.slice()]), }, ]) .pipeThrough(new TarStream()) .pipeThrough(new UntarStream()); let buffer = new Uint8Array(); for await (const entry of readable) { if (entry.readable) buffer = await toBytes(entry.readable); } assertEquals(buffer, data); }); Deno.test("UntarStream() with invalid size", async () => { const bytes = (await toBytes( ReadableStream.from([ { type: "file", path: "newFile.txt", size: 512, readable: ReadableStream.from([new Uint8Array(512).fill(97)]), }, ]) .pipeThrough(new TarStream()), )).slice(0, -100); const readable = ReadableStream.from([bytes]) .pipeThrough(new UntarStream()); await assertRejects( async () => { for await (const entry of readable) await entry.readable?.cancel(); }, RangeError, "Cannot extract the tar archive: The tarball chunk has an unexpected number of bytes (412)", ); }); Deno.test("UntarStream() with invalid ending", async () => { const tarBytes = await toBytes( ReadableStream.from([ { type: "file", path: "newFile.txt", size: 512, readable: ReadableStream.from([new Uint8Array(512).fill(97)]), }, ]) .pipeThrough(new TarStream()), ); tarBytes[tarBytes.length - 1] = 1; const readable = ReadableStream.from([tarBytes]) .pipeThrough(new UntarStream()); await assertRejects( async () => { for await (const entry of readable) await entry.readable?.cancel(); }, TypeError, "Cannot extract the tar archive: The tarball has invalid ending", ); }); Deno.test("UntarStream() with too small size", async () => { const readable = ReadableStream.from([new Uint8Array(512)]) .pipeThrough(new UntarStream()); await assertRejects( async () => { for await (const entry of readable) await entry.readable?.cancel(); }, RangeError, "Cannot extract the tar archive: The tarball is too small to be valid", ); }); Deno.test("UntarStream() with invalid checksum", async () => { const tarBytes = await toBytes( ReadableStream.from([ { type: "file", path: "newFile.txt", size: 512, readable: ReadableStream.from([new Uint8Array(512).fill(97)]), }, ]) .pipeThrough(new TarStream()), ); tarBytes[148] = 97; const readable = ReadableStream.from([tarBytes]) .pipeThrough(new UntarStream()); await assertRejects( async () => { for await (const entry of readable) await entry.readable?.cancel(); }, Error, "Cannot extract the tar archive: An archive entry has invalid header checksum", ); }); Deno.test("UntarStream() with extra bytes", async () => { const readable = ReadableStream.from([ { type: "directory", path: "a", }, ]) .pipeThrough(new TarStream()) .pipeThrough( new TransformStream({ flush(controller) { controller.enqueue(new Uint8Array(512 * 2).fill(1)); }, }), ) .pipeThrough(new UntarStream()); for await (const entry of readable) { assertEquals(entry.path, "a"); entry.readable?.cancel(); } });