mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
fix(tar): ignore non-tar file portion of a stream in UntarStream
(#6064)
This commit is contained in:
parent
361be4569d
commit
e43a7df4b6
@ -1,12 +1,13 @@
|
|||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
|
||||||
|
import { concat } from "@std/bytes";
|
||||||
import {
|
import {
|
||||||
assertValidTarStreamOptions,
|
assertValidTarStreamOptions,
|
||||||
TarStream,
|
TarStream,
|
||||||
type TarStreamInput,
|
type TarStreamInput,
|
||||||
} from "./tar_stream.ts";
|
} from "./tar_stream.ts";
|
||||||
import { assertEquals, assertRejects, assertThrows } from "../assert/mod.ts";
|
|
||||||
import { UntarStream } from "./untar_stream.ts";
|
import { UntarStream } from "./untar_stream.ts";
|
||||||
import { concat } from "../bytes/mod.ts";
|
|
||||||
|
|
||||||
Deno.test("TarStream() with default stream", async () => {
|
Deno.test("TarStream() with default stream", async () => {
|
||||||
const text = new TextEncoder().encode("Hello World!");
|
const text = new TextEncoder().encode("Hello World!");
|
||||||
|
@ -176,7 +176,8 @@ export class UntarStream
|
|||||||
implements TransformStream<Uint8Array, TarStreamEntry> {
|
implements TransformStream<Uint8Array, TarStreamEntry> {
|
||||||
#readable: ReadableStream<TarStreamEntry>;
|
#readable: ReadableStream<TarStreamEntry>;
|
||||||
#writable: WritableStream<Uint8Array>;
|
#writable: WritableStream<Uint8Array>;
|
||||||
#gen: AsyncGenerator<Uint8Array>;
|
#reader: ReadableStreamDefaultReader<Uint8Array>;
|
||||||
|
#buffer: Uint8Array[] = [];
|
||||||
#lock = false;
|
#lock = false;
|
||||||
constructor() {
|
constructor() {
|
||||||
const { readable, writable } = new TransformStream<
|
const { readable, writable } = new TransformStream<
|
||||||
@ -185,43 +186,50 @@ export class UntarStream
|
|||||||
>();
|
>();
|
||||||
this.#readable = ReadableStream.from(this.#untar());
|
this.#readable = ReadableStream.from(this.#untar());
|
||||||
this.#writable = writable;
|
this.#writable = writable;
|
||||||
|
this.#reader = readable.pipeThrough(new FixedChunkStream(512)).getReader();
|
||||||
|
}
|
||||||
|
|
||||||
this.#gen = async function* () {
|
async #read(): Promise<Uint8Array | undefined> {
|
||||||
const buffer: Uint8Array[] = [];
|
const { done, value } = await this.#reader.read();
|
||||||
for await (
|
if (done) return undefined;
|
||||||
const chunk of readable.pipeThrough(new FixedChunkStream(512))
|
if (value.length !== 512) {
|
||||||
) {
|
throw new RangeError(
|
||||||
if (chunk.length !== 512) {
|
`Cannot extract the tar archive: The tarball chunk has an unexpected number of bytes (${value.length})`,
|
||||||
throw new RangeError(
|
);
|
||||||
`Cannot extract the tar archive: The tarball chunk has an unexpected number of bytes (${chunk.length})`,
|
}
|
||||||
);
|
this.#buffer.push(value);
|
||||||
}
|
return this.#buffer.shift();
|
||||||
|
}
|
||||||
|
|
||||||
buffer.push(chunk);
|
async *#untar(): AsyncGenerator<TarStreamEntry> {
|
||||||
if (buffer.length > 2) yield buffer.shift()!;
|
for (let i = 0; i < 2; ++i) {
|
||||||
}
|
const { done, value } = await this.#reader.read();
|
||||||
if (buffer.length < 2) {
|
if (done || value.length !== 512) {
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
"Cannot extract the tar archive: The tarball is too small to be valid",
|
"Cannot extract the tar archive: The tarball is too small to be valid",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!buffer.every((value) => value.every((x) => x === 0))) {
|
this.#buffer.push(value);
|
||||||
throw new TypeError(
|
}
|
||||||
"Cannot extract the tar archive: The tarball has invalid ending",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
async *#untar(): AsyncGenerator<TarStreamEntry> {
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
while (true) {
|
while (true) {
|
||||||
while (this.#lock) {
|
while (this.#lock) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { done, value } = await this.#gen.next();
|
// Check for premature ending
|
||||||
if (done) break;
|
if (this.#buffer.every((value) => value.every((x) => x === 0))) {
|
||||||
|
await this.#reader.cancel("Tar stream finished prematurely");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = await this.#read();
|
||||||
|
if (value == undefined) {
|
||||||
|
if (this.#buffer.every((value) => value.every((x) => x === 0))) break;
|
||||||
|
throw new TypeError(
|
||||||
|
"Cannot extract the tar archive: The tarball has invalid ending",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Validate Checksum
|
// Validate Checksum
|
||||||
const checksum = parseInt(
|
const checksum = parseInt(
|
||||||
@ -286,8 +294,8 @@ export class UntarStream
|
|||||||
|
|
||||||
async *#genFile(size: number): AsyncGenerator<Uint8Array> {
|
async *#genFile(size: number): AsyncGenerator<Uint8Array> {
|
||||||
for (let i = Math.ceil(size / 512); i > 0; --i) {
|
for (let i = Math.ceil(size / 512); i > 0; --i) {
|
||||||
const { done, value } = await this.#gen.next();
|
const value = await this.#read();
|
||||||
if (done) {
|
if (value == undefined) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
"Cannot extract the tar archive: Unexpected end of Tarball",
|
"Cannot extract the tar archive: Unexpected end of Tarball",
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
import { concat } from "../bytes/mod.ts";
|
|
||||||
|
import { assertEquals, assertRejects } from "@std/assert";
|
||||||
|
import { toBytes } from "@std/streams/unstable-to-bytes";
|
||||||
import { TarStream, type TarStreamInput } from "./tar_stream.ts";
|
import { TarStream, type TarStreamInput } from "./tar_stream.ts";
|
||||||
import {
|
import {
|
||||||
type OldStyleFormat,
|
type OldStyleFormat,
|
||||||
type PosixUstarFormat,
|
type PosixUstarFormat,
|
||||||
UntarStream,
|
UntarStream,
|
||||||
} from "./untar_stream.ts";
|
} from "./untar_stream.ts";
|
||||||
import { assertEquals, assertRejects } from "../assert/mod.ts";
|
|
||||||
|
|
||||||
Deno.test("expandTarArchiveCheckingHeaders", async () => {
|
Deno.test("expandTarArchiveCheckingHeaders", async () => {
|
||||||
const text = new TextEncoder().encode("Hello World!");
|
const text = new TextEncoder().encode("Hello World!");
|
||||||
@ -39,9 +40,9 @@ Deno.test("expandTarArchiveCheckingHeaders", async () => {
|
|||||||
.pipeThrough(new UntarStream());
|
.pipeThrough(new UntarStream());
|
||||||
|
|
||||||
const headers: (OldStyleFormat | PosixUstarFormat)[] = [];
|
const headers: (OldStyleFormat | PosixUstarFormat)[] = [];
|
||||||
for await (const item of readable) {
|
for await (const entry of readable) {
|
||||||
headers.push(item.header);
|
headers.push(entry.header);
|
||||||
await item.readable?.cancel();
|
await entry.readable?.cancel();
|
||||||
}
|
}
|
||||||
assertEquals(headers, [{
|
assertEquals(headers, [{
|
||||||
name: "./potato",
|
name: "./potato",
|
||||||
@ -98,9 +99,7 @@ Deno.test("expandTarArchiveCheckingBodies", async () => {
|
|||||||
|
|
||||||
let buffer = new Uint8Array();
|
let buffer = new Uint8Array();
|
||||||
for await (const item of readable) {
|
for await (const item of readable) {
|
||||||
if (item.readable) {
|
if (item.readable) buffer = await toBytes(item.readable);
|
||||||
buffer = concat(await Array.fromAsync(item.readable));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assertEquals(buffer, text);
|
assertEquals(buffer, text);
|
||||||
});
|
});
|
||||||
@ -125,59 +124,47 @@ Deno.test("UntarStream() with size equals to multiple of 512", async () => {
|
|||||||
|
|
||||||
let buffer = new Uint8Array();
|
let buffer = new Uint8Array();
|
||||||
for await (const entry of readable) {
|
for await (const entry of readable) {
|
||||||
if (entry.readable) {
|
if (entry.readable) buffer = await toBytes(entry.readable);
|
||||||
buffer = concat(await Array.fromAsync(entry.readable));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assertEquals(buffer, data);
|
assertEquals(buffer, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("UntarStream() with invalid size", async () => {
|
Deno.test("UntarStream() with invalid size", async () => {
|
||||||
const readable = ReadableStream.from<TarStreamInput>([
|
const bytes = (await toBytes(
|
||||||
{
|
ReadableStream.from<TarStreamInput>([
|
||||||
type: "file",
|
{
|
||||||
path: "newFile.txt",
|
type: "file",
|
||||||
size: 512,
|
path: "newFile.txt",
|
||||||
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
size: 512,
|
||||||
},
|
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
||||||
])
|
},
|
||||||
.pipeThrough(new TarStream())
|
])
|
||||||
.pipeThrough(
|
.pipeThrough(new TarStream()),
|
||||||
new TransformStream<Uint8Array, Uint8Array>({
|
)).slice(0, -100);
|
||||||
flush(controller) {
|
|
||||||
controller.enqueue(new Uint8Array(100));
|
const readable = ReadableStream.from([bytes])
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipeThrough(new UntarStream());
|
.pipeThrough(new UntarStream());
|
||||||
|
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
for await (const entry of readable) {
|
for await (const entry of readable) await entry.readable?.cancel();
|
||||||
if (entry.readable) {
|
|
||||||
// deno-lint-ignore no-empty
|
|
||||||
for await (const _ of entry.readable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
RangeError,
|
RangeError,
|
||||||
"Cannot extract the tar archive: The tarball chunk has an unexpected number of bytes (100)",
|
"Cannot extract the tar archive: The tarball chunk has an unexpected number of bytes (412)",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("UntarStream() with invalid ending", async () => {
|
Deno.test("UntarStream() with invalid ending", async () => {
|
||||||
const tarBytes = concat(
|
const tarBytes = await toBytes(
|
||||||
await Array.fromAsync(
|
ReadableStream.from<TarStreamInput>([
|
||||||
ReadableStream.from<TarStreamInput>([
|
{
|
||||||
{
|
type: "file",
|
||||||
type: "file",
|
path: "newFile.txt",
|
||||||
path: "newFile.txt",
|
size: 512,
|
||||||
size: 512,
|
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
||||||
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
},
|
||||||
},
|
])
|
||||||
])
|
.pipeThrough(new TarStream()),
|
||||||
.pipeThrough(new TarStream()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
tarBytes[tarBytes.length - 1] = 1;
|
tarBytes[tarBytes.length - 1] = 1;
|
||||||
|
|
||||||
@ -186,12 +173,7 @@ Deno.test("UntarStream() with invalid ending", async () => {
|
|||||||
|
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
for await (const entry of readable) {
|
for await (const entry of readable) await entry.readable?.cancel();
|
||||||
if (entry.readable) {
|
|
||||||
// deno-lint-ignore no-empty
|
|
||||||
for await (const _ of entry.readable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
TypeError,
|
TypeError,
|
||||||
"Cannot extract the tar archive: The tarball has invalid ending",
|
"Cannot extract the tar archive: The tarball has invalid ending",
|
||||||
@ -204,12 +186,7 @@ Deno.test("UntarStream() with too small size", async () => {
|
|||||||
|
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
for await (const entry of readable) {
|
for await (const entry of readable) await entry.readable?.cancel();
|
||||||
if (entry.readable) {
|
|
||||||
// deno-lint-ignore no-empty
|
|
||||||
for await (const _ of entry.readable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
RangeError,
|
RangeError,
|
||||||
"Cannot extract the tar archive: The tarball is too small to be valid",
|
"Cannot extract the tar archive: The tarball is too small to be valid",
|
||||||
@ -217,18 +194,16 @@ Deno.test("UntarStream() with too small size", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("UntarStream() with invalid checksum", async () => {
|
Deno.test("UntarStream() with invalid checksum", async () => {
|
||||||
const tarBytes = concat(
|
const tarBytes = await toBytes(
|
||||||
await Array.fromAsync(
|
ReadableStream.from<TarStreamInput>([
|
||||||
ReadableStream.from<TarStreamInput>([
|
{
|
||||||
{
|
type: "file",
|
||||||
type: "file",
|
path: "newFile.txt",
|
||||||
path: "newFile.txt",
|
size: 512,
|
||||||
size: 512,
|
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
||||||
readable: ReadableStream.from([new Uint8Array(512).fill(97)]),
|
},
|
||||||
},
|
])
|
||||||
])
|
.pipeThrough(new TarStream()),
|
||||||
.pipeThrough(new TarStream()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
tarBytes[148] = 97;
|
tarBytes[148] = 97;
|
||||||
|
|
||||||
@ -237,14 +212,32 @@ Deno.test("UntarStream() with invalid checksum", async () => {
|
|||||||
|
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
for await (const entry of readable) {
|
for await (const entry of readable) await entry.readable?.cancel();
|
||||||
if (entry.readable) {
|
|
||||||
// deno-lint-ignore no-empty
|
|
||||||
for await (const _ of entry.readable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Error,
|
Error,
|
||||||
"Cannot extract the tar archive: An archive entry has invalid header checksum",
|
"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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user