-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathparse.ts
108 lines (85 loc) · 2.9 KB
/
parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import type { ParsedTarFileItem, TarFileItem } from "./types";
export function parseTar(data: ArrayBuffer | Uint8Array): TarFileItem[] {
const buffer = (data as Uint8Array).buffer || data;
const files: ParsedTarFileItem[] = [];
let offset = 0;
while (offset < buffer.byteLength - 512) {
// File name (offset: 0 - length: 100)
const name = _readString(buffer, offset, 100);
if (name.length === 0) {
break;
}
// File mode (offset: 100 - length: 8)
const mode = _readString(buffer, offset + 100, 8);
// File uid (offset: 108 - length: 8)
const uid = Number.parseInt(_readString(buffer, offset + 108, 8));
// File gid (offset: 116 - length: 8)
const gid = Number.parseInt(_readString(buffer, offset + 116, 8));
// File size (offset: 124 - length: 12)
const size = _readNumber(buffer, offset + 124, 12);
// File mtime (offset: 136 - length: 12)
const mtime = _readNumber(buffer, offset + 136, 12);
// File type (offset: 156 - length: 1)
const _type = _readNumber(buffer, offset + 156, 1);
const type = _type === 0 ? "file" : (_type === 5 ? "directory" : _type); // prettier-ignore
// Ustar indicator (offset: 257 - length: 6)
// Ignore
// Ustar version (offset: 263 - length: 2)
// Ignore
// File owner user (offset: 265 - length: 32)
const user = _readString(buffer, offset + 265, 32);
// File owner group (offset: 297 - length: 32)
const group = _readString(buffer, offset + 297, 32);
// File data (offset: 512 - length: size)
const data = new Uint8Array(buffer, offset + 512, size);
files.push({
name,
type,
size,
data,
get text() {
return new TextDecoder().decode(this.data);
},
attrs: {
mode,
uid,
gid,
mtime,
user,
group,
},
});
offset += 512 + 512 * Math.trunc(size / 512);
if (size % 512) {
offset += 512;
}
}
return files;
}
export async function parseTarGzip(
data: ArrayBuffer | Uint8Array,
opts: { compression?: CompressionFormat } = {},
): Promise<TarFileItem[]> {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array(data));
controller.close();
},
}).pipeThrough(new DecompressionStream(opts.compression ?? "gzip"));
const decompressedData = await new Response(stream).arrayBuffer();
return parseTar(decompressedData);
}
function _readString(buffer: ArrayBuffer, offset: number, size: number) {
const view = new Uint8Array(buffer, offset, size);
const i = view.indexOf(0);
const td = new TextDecoder();
return td.decode(view.slice(0, i));
}
function _readNumber(buffer: ArrayBuffer, offset: number, size: number) {
const view = new Uint8Array(buffer, offset, size);
let str = "";
for (let i = 0; i < size; i++) {
str += String.fromCodePoint(view[i]);
}
return Number.parseInt(str, 8);
}