std/tar/untar_stream_test.ts

244 lines
5.9 KiB
TypeScript

// 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<TarStreamInput>([
{
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<TarStreamInput>([
{
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<TarStreamInput>([
{
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<TarStreamInput>([
{
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<TarStreamInput>([
{
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<TarStreamInput>([
{
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<TarStreamInput>([
{
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();
}
});