Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: web streams based archive/tar #1985

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a1f46c7
tar stream
crowlKats Jan 25, 2022
336b5d9
fix
crowlKats Jan 25, 2022
26498d4
fmt
crowlKats Jan 25, 2022
c9d1303
clean
crowlKats Jan 25, 2022
f35c82a
clean
crowlKats Jan 26, 2022
8f3b56d
work
crowlKats Jan 27, 2022
fa77bbd
Update copyright header
crowlKats Mar 8, 2022
339389d
Update copyright header
crowlKats Mar 8, 2022
db8fab7
Merge branch 'main' into tar_stream
crowlKats Nov 18, 2023
737baa3
fix
crowlKats Nov 24, 2023
c9913e8
Merge branch 'main' into tar_stream
iuioiua Dec 7, 2023
8851c9e
fix untar_stream readablestream usage
crowlKats Dec 15, 2023
5b7ac58
fix
iuioiua Dec 18, 2023
74ac800
fix
iuioiua Dec 18, 2023
8b44952
fix
iuioiua Dec 18, 2023
34d3ecc
remove duplicate test
iuioiua Dec 18, 2023
9e74a06
lint
iuioiua Dec 18, 2023
c52d5b8
Merge branch 'main' into tar_stream
iuioiua Dec 18, 2023
bab208a
fix lint
iuioiua Dec 18, 2023
904e33a
fix and more tests
crowlKats Dec 18, 2023
6bdce35
clean up
crowlKats Dec 18, 2023
999a4db
more tests
crowlKats Dec 18, 2023
74f7053
move
crowlKats Dec 18, 2023
f207438
more tests and readd link feature
crowlKats Dec 18, 2023
e81aa8f
fmt
crowlKats Dec 18, 2023
bbf4b1e
Merge branch 'main' into tar_stream
crowlKats Dec 18, 2023
4dc5ee5
fix
crowlKats Dec 18, 2023
8bedbd1
clean
crowlKats Dec 18, 2023
226c31c
remove debug
crowlKats Dec 18, 2023
5744607
remove duplicate tests
crowlKats Dec 18, 2023
74be599
fix: `directoryEntryType` test
iuioiua Dec 19, 2023
c89bfda
cleanup
iuioiua Dec 19, 2023
4ce1cbe
cleanup
iuioiua Dec 19, 2023
98c8778
test cleanups
iuioiua Dec 20, 2023
f49b680
refactor: remove `clean()`
iuioiua Dec 20, 2023
9a57221
refactor: cleanup `pad()`
iuioiua Dec 20, 2023
52c0924
refactor: cleanup logic for long filename
iuioiua Dec 20, 2023
c2e9bdf
refactor: remove unnecessary logic
iuioiua Dec 20, 2023
907aeb5
refactor: replace `FileTypes` enum with array
iuioiua Dec 20, 2023
d06f737
refactor: cleanup `getTarOptions()`
iuioiua Dec 21, 2023
7dfccc5
refactor: use up `buffer.readable`
iuioiua Dec 21, 2023
97eba04
refactor: minor tweaks
iuioiua Dec 21, 2023
8cf47b8
revert
iuioiua Dec 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions archive/_stream_common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

export const recordSize = 512;

export const ustarStructure = [
{
field: "fileName",
length: 100,
},
{
field: "fileMode",
length: 8,
},
{
field: "uid",
length: 8,
},
{
field: "gid",
length: 8,
},
{
field: "fileSize",
length: 12,
},
{
field: "mtime",
length: 12,
},
{
field: "checksum",
length: 8,
},
{
field: "type",
length: 1,
},
{
field: "linkName",
length: 100,
},
{
field: "ustar",
length: 8,
},
{
field: "owner",
length: 32,
},
{
field: "group",
length: 32,
},
{
field: "majorNumber",
length: 8,
},
{
field: "minorNumber",
length: 8,
},
{
field: "fileNamePrefix",
length: 155,
},
{
field: "padding",
length: 12,
},
] as const;

export const FILE_TYPES = [
"file",
"link",
"symlink",
"character-device",
"block-device",
"directory",
"fifo",
"contiguous-file",
] as const;

export type FileType = typeof FILE_TYPES[number];

export interface TarInfo {
fileMode?: number;
mtime?: number;
uid?: number;
gid?: number;
owner?: string;
group?: string;
type?: FileType;
}
212 changes: 212 additions & 0 deletions archive/tar_stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
/**
* Ported and modified from: https://github.com/beatgammit/tar-js and
* licensed as:
*
* (The MIT License)
*
* Copyright (c) 2011 T. Jameson Little
* Copyright (c) 2019 Jun Kato
* Copyright (c) 2018-2022 the Deno authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import {
FILE_TYPES,
recordSize,
TarInfo,
ustarStructure,
} from "./_stream_common.ts";

const ustar = "ustar\u000000";

function pad(num: number, bytes: number): string {
return num.toString(8).padStart(bytes, "0");
}
/*
struct posix_header { // byte offset
char name[100]; // 0
char mode[8]; // 100
char uid[8]; // 108
char gid[8]; // 116
char size[12]; // 124
char mtime[12]; // 136
char chksum[8]; // 148
char typeflag; // 156
char linkname[100]; // 157
char magic[6]; // 257
char version[2]; // 263
char uname[32]; // 265
char gname[32]; // 297
char devmajor[8]; // 329
char devminor[8]; // 337
char prefix[155]; // 345
// 500
};
*/

/**
* Create header for a file in a tar archive
*/
function formatHeader(data: TarData): Uint8Array {
const encoder = new TextEncoder();
const buffer = new Uint8Array(512);
let offset = 0;
for (const value of ustarStructure) {
const entry = encoder.encode(data[value.field as keyof TarData]);
buffer.set(entry, offset);
offset += value.length; // space it out with nulls
}
return buffer;
}

export interface TarData {
fileName?: string;
fileNamePrefix?: string;
fileMode?: string;
uid?: string;
gid?: string;
fileSize: string;
mtime?: string;
checksum?: string;
type?: string;
ustar?: string;
owner?: string;
group?: string;
}

export interface TarDataWithSource extends TarData {
/**
* buffer to read
*/
readable: ReadableStream<Uint8Array>;
}

export interface TarOptions extends TarInfo {
/**
* file name
*/
name: string;

/**
* append any arbitrary content
*/
readable: ReadableStream<Uint8Array>;

/**
* size of the content to be appended
*/
contentSize: number;
}

/**
* A class to create a tar archive
*/
export class TarStream extends TransformStream<TarOptions, Uint8Array> {
constructor() {
super({
transform: async (chunk: TarOptions, controller) => {
// separate file name into two parts if needed
let fileNamePrefix: string | undefined;
let fileName = chunk.name;
if (fileName.length > 100) {
const i = fileName.lastIndexOf("/", 155);
if (i >= 0) {
fileNamePrefix = fileName.substring(0, i);
fileName = fileName.substring(i + 1);
} else {
throw new Error(
"ustar format does not allow a long file name (length of [file name prefix] + / + [file name] must be shorter than 256 bytes)",
);
}
if (fileNamePrefix.length > 155) {
throw new Error(
"ustar format does not allow a long file name (length of [file name prefix] + / + [file name] must be shorter than 256 bytes)",
);
}
}
const mode = chunk.fileMode || parseInt("777", 8) & 0xfff;
const mtime = Math.floor(chunk.mtime ?? new Date().valueOf() / 1000);
const uid = chunk.uid || 0;
const gid = chunk.gid || 0;

if (typeof chunk.owner === "string" && chunk.owner.length >= 32) {
throw new Error(
"ustar format does not allow owner name length >= 32 bytes",
);
}
if (typeof chunk.group === "string" && chunk.group.length >= 32) {
throw new Error(
"ustar format does not allow group name length >= 32 bytes",
);
}

const type = FILE_TYPES.indexOf(chunk.type ?? "file");

const tarData: TarDataWithSource = {
fileName,
fileNamePrefix,
fileMode: pad(mode, 7),
uid: pad(uid, 7),
gid: pad(gid, 7),
fileSize: pad(chunk.contentSize, 11),
mtime: pad(mtime, 11),
checksum: " ",
type: type.toString(),
ustar,
owner: chunk.owner || "",
group: chunk.group || "",
readable: chunk.readable,
};

// calculate the checksum
let checksum = 0;
const encoder = new TextEncoder();
for (const key in tarData) {
if (key === "readable") {
continue;
}
checksum += encoder.encode(tarData[key as keyof TarData]).reduce(
(p, c) => p + c,
0,
);
}

tarData.checksum = pad(checksum, 6) + "\u0000 ";

controller.enqueue(formatHeader(tarData));

for await (const readableChunk of chunk.readable) {
controller.enqueue(readableChunk);
}

controller.enqueue(
new Uint8Array(
recordSize -
(parseInt(tarData.fileSize, 8) % recordSize || recordSize),
),
);
},
flush(controller) {
// append 2 empty records
controller.enqueue(new Uint8Array(recordSize * 2));
},
});
}
}
Loading
Loading