@oh-my-pi/pi-utils 17.3.8 → 17.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +22 -0
  3. package/THIRD-PARTY-NOTICES.txt +22909 -0
  4. package/dist/types/ar/arj.d.ts +5 -0
  5. package/dist/types/ar/asar.d.ts +7 -0
  6. package/dist/types/ar/bytes.d.ts +20 -0
  7. package/dist/types/ar/cab.d.ts +5 -0
  8. package/dist/types/ar/checksums.d.ts +10 -0
  9. package/dist/types/ar/codecs/bzip2.d.ts +4 -0
  10. package/dist/types/ar/codecs/gzip.d.ts +6 -0
  11. package/dist/types/ar/codecs/lzma.d.ts +6 -0
  12. package/dist/types/ar/codecs/lzw.d.ts +4 -0
  13. package/dist/types/ar/codecs/lzx.d.ts +7 -0
  14. package/dist/types/ar/codecs/xz.d.ts +4 -0
  15. package/dist/types/ar/codecs/zstd.d.ts +6 -0
  16. package/dist/types/ar/cpio.d.ts +7 -0
  17. package/dist/types/ar/deb.d.ts +5 -0
  18. package/dist/types/ar/entries.d.ts +22 -0
  19. package/dist/types/ar/error.d.ts +8 -0
  20. package/dist/types/ar/index.d.ts +11 -0
  21. package/dist/types/ar/iso.d.ts +5 -0
  22. package/dist/types/ar/limits.d.ts +33 -0
  23. package/dist/types/ar/lzh.d.ts +7 -0
  24. package/dist/types/ar/open.d.ts +45 -0
  25. package/dist/types/ar/paths.d.ts +18 -0
  26. package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
  27. package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
  28. package/dist/types/ar/rar.d.ts +5 -0
  29. package/dist/types/ar/reader.d.ts +28 -0
  30. package/dist/types/ar/registry.d.ts +17 -0
  31. package/dist/types/ar/rpm.d.ts +5 -0
  32. package/dist/types/ar/sevenzip/decode.d.ts +33 -0
  33. package/dist/types/ar/sevenzip.d.ts +5 -0
  34. package/dist/types/ar/source.d.ts +54 -0
  35. package/dist/types/ar/tar.d.ts +9 -0
  36. package/dist/types/ar/types.d.ts +99 -0
  37. package/dist/types/ar/unix-ar.d.ts +7 -0
  38. package/dist/types/ar/write.d.ts +10 -0
  39. package/dist/types/ar/zip.d.ts +10 -0
  40. package/dist/types/dirs.d.ts +6 -0
  41. package/package.json +9 -3
  42. package/src/ar/arj.ts +314 -0
  43. package/src/ar/asar.ts +480 -0
  44. package/src/ar/bytes.ts +78 -0
  45. package/src/ar/cab.ts +377 -0
  46. package/src/ar/checksums.ts +62 -0
  47. package/src/ar/codecs/bzip2.ts +489 -0
  48. package/src/ar/codecs/gzip.ts +25 -0
  49. package/src/ar/codecs/lzma.ts +437 -0
  50. package/src/ar/codecs/lzw.ts +191 -0
  51. package/src/ar/codecs/lzx.ts +366 -0
  52. package/src/ar/codecs/xz.ts +522 -0
  53. package/src/ar/codecs/zstd.ts +25 -0
  54. package/src/ar/cpio.ts +389 -0
  55. package/src/ar/deb.ts +160 -0
  56. package/src/ar/entries.ts +97 -0
  57. package/src/ar/error.ts +11 -0
  58. package/src/ar/index.ts +16 -0
  59. package/src/ar/iso.ts +712 -0
  60. package/src/ar/limits.ts +80 -0
  61. package/src/ar/lzh.ts +659 -0
  62. package/src/ar/open.ts +225 -0
  63. package/src/ar/paths.ts +70 -0
  64. package/src/ar/rar/rar4-decoder.ts +459 -0
  65. package/src/ar/rar/rar5-decoder.ts +400 -0
  66. package/src/ar/rar.ts +735 -0
  67. package/src/ar/reader.ts +162 -0
  68. package/src/ar/registry.ts +208 -0
  69. package/src/ar/rpm.ts +320 -0
  70. package/src/ar/sevenzip/decode.ts +239 -0
  71. package/src/ar/sevenzip.ts +634 -0
  72. package/src/ar/source.ts +190 -0
  73. package/src/ar/tar.ts +771 -0
  74. package/src/ar/types.ts +131 -0
  75. package/src/ar/unix-ar.ts +312 -0
  76. package/src/ar/write.ts +55 -0
  77. package/src/ar/zip.ts +719 -0
  78. package/src/browsers.ts +2 -149
  79. package/src/dirs.ts +17 -2
  80. package/src/docx/converter.ts +9 -9
  81. package/dist/types/docx/zip.d.ts +0 -6
  82. package/src/docx/zip.ts +0 -87
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "17.3.8",
4
+ "version": "17.4.1",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
- "author": "Can Boluk",
7
+ "author": "Stencil Labs, Inc.",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "17.3.8"
34
+ "@oh-my-pi/pi-natives": "17.4.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/bun": "^1.3.14"
@@ -43,6 +43,8 @@
43
43
  "src",
44
44
  "README.md",
45
45
  "CHANGELOG.md",
46
+ "LICENSE",
47
+ "THIRD-PARTY-NOTICES.txt",
46
48
  "dist/types"
47
49
  ],
48
50
  "exports": {
@@ -50,6 +52,10 @@
50
52
  "types": "./dist/types/index.d.ts",
51
53
  "import": "./src/index.ts"
52
54
  },
55
+ "./ar": {
56
+ "types": "./dist/types/ar/index.d.ts",
57
+ "import": "./src/ar/index.ts"
58
+ },
53
59
  "./*": {
54
60
  "types": "./dist/types/*.d.ts",
55
61
  "import": "./src/*.ts"
package/src/ar/arj.ts ADDED
@@ -0,0 +1,314 @@
1
+ import { crc32 } from "./checksums";
2
+ import { ArchiveError } from "./error";
3
+ import { assertArchiveMemberSize, assertEntryCount, assertIndexSize, assertInMemorySize } from "./limits";
4
+ import { decompressLhStatic } from "./lzh";
5
+ import { assertArchivePathBytes, assertArchivePathString, normalizeArchiveEntryPath } from "./paths";
6
+ import { type ByteSource, readAllBytes } from "./source";
7
+ import type { ArchiveIndexEntry, FormatReader, FormatReadOptions, MemberSource } from "./types";
8
+
9
+ const ARJ_SIGNATURE_0 = 0x60;
10
+ const ARJ_SIGNATURE_1 = 0xea;
11
+ const ARJ_MAX_BASIC_HEADER = 2600;
12
+ const LEGACY_DECODER = new TextDecoder("windows-1252");
13
+
14
+ function u16(bytes: Uint8Array, offset: number): number {
15
+ return bytes[offset]! | (bytes[offset + 1]! << 8);
16
+ }
17
+
18
+ function u32(bytes: Uint8Array, offset: number): number {
19
+ return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
20
+ }
21
+
22
+ function assertRange(bytes: Uint8Array, start: number, end: number, what: string): void {
23
+ if (
24
+ !Number.isSafeInteger(start) ||
25
+ !Number.isSafeInteger(end) ||
26
+ start < 0 ||
27
+ end < start ||
28
+ end > bytes.byteLength
29
+ ) {
30
+ throw new ArchiveError(`Invalid ARJ archive: truncated ${what}`);
31
+ }
32
+ }
33
+
34
+ function dosTimeToMs(value: number): number | undefined {
35
+ if (value === 0) return undefined;
36
+ const year = 1980 + ((value >>> 25) & 0x7f);
37
+ const month = (value >>> 21) & 0x0f;
38
+ const day = (value >>> 16) & 0x1f;
39
+ const hour = (value >>> 11) & 0x1f;
40
+ const minute = (value >>> 5) & 0x3f;
41
+ const second = (value & 0x1f) * 2;
42
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;
43
+ return Date.UTC(year, month - 1, day, hour, minute, second);
44
+ }
45
+
46
+ function readCString(bytes: Uint8Array, start: number, end: number, field: string): { value: string; next: number } {
47
+ let terminator = start;
48
+ while (terminator < end && bytes[terminator] !== 0) terminator++;
49
+ if (terminator === end) throw new ArchiveError(`Invalid ARJ ${field}: missing terminator`);
50
+ return { value: LEGACY_DECODER.decode(bytes.subarray(start, terminator)), next: terminator + 1 };
51
+ }
52
+
53
+ interface ArjBlock {
54
+ bodyStart: number;
55
+ bodySize: number;
56
+ nextOffset: number;
57
+ metadataSize: number;
58
+ isEnd: boolean;
59
+ }
60
+
61
+ function parseArjBlock(bytes: Uint8Array, offset: number, options: FormatReadOptions): ArjBlock {
62
+ assertRange(bytes, offset, offset + 4, "header signature");
63
+ if (bytes[offset] !== ARJ_SIGNATURE_0 || bytes[offset + 1] !== ARJ_SIGNATURE_1) {
64
+ throw new ArchiveError("Invalid ARJ header signature");
65
+ }
66
+ const bodySize = u16(bytes, offset + 2);
67
+ if (bodySize === 0)
68
+ return { bodyStart: offset + 4, bodySize: 0, nextOffset: offset + 4, metadataSize: 4, isEnd: true };
69
+ if (bodySize < 30 || bodySize > ARJ_MAX_BASIC_HEADER) throw new ArchiveError("Invalid ARJ basic header size");
70
+ const bodyStart = offset + 4;
71
+ const bodyEnd = bodyStart + bodySize;
72
+ assertRange(bytes, bodyStart, bodyEnd + 4, "basic header");
73
+ if (crc32(bytes.subarray(bodyStart, bodyEnd)) !== u32(bytes, bodyEnd)) {
74
+ throw new ArchiveError("Invalid ARJ basic header CRC32");
75
+ }
76
+ let cursor = bodyEnd + 4;
77
+ let extensionCount = 0;
78
+ for (;;) {
79
+ assertRange(bytes, cursor, cursor + 2, "extended header size");
80
+ const extensionSize = u16(bytes, cursor);
81
+ cursor += 2;
82
+ if (extensionSize === 0) break;
83
+ if (++extensionCount > 65_535) throw new ArchiveError("Invalid ARJ archive: too many extended headers");
84
+ assertRange(bytes, cursor, cursor + extensionSize + 4, "extended header");
85
+ if (crc32(bytes.subarray(cursor, cursor + extensionSize)) !== u32(bytes, cursor + extensionSize)) {
86
+ throw new ArchiveError("Invalid ARJ extended header CRC32");
87
+ }
88
+ cursor += extensionSize + 4;
89
+ assertIndexSize(cursor - offset, options.limits, "header metadata");
90
+ }
91
+ return { bodyStart, bodySize, nextOffset: cursor, metadataSize: cursor - offset, isEnd: false };
92
+ }
93
+
94
+ class ArjBitReader {
95
+ readonly #bytes: Uint8Array;
96
+ #position = 0;
97
+
98
+ constructor(bytes: Uint8Array) {
99
+ this.#bytes = bytes;
100
+ }
101
+
102
+ read(count: number): number {
103
+ if (!Number.isInteger(count) || count < 0 || count > 24 || this.#position + count > this.#bytes.byteLength * 8) {
104
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: truncated bitstream");
105
+ }
106
+ let value = 0;
107
+ for (let index = 0; index < count; index++) {
108
+ const position = this.#position++;
109
+ value = value * 2 + ((this.#bytes[position >>> 3]! >>> (7 - (position & 7))) & 1);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ assertZeroPadding(): void {
115
+ while (this.#position < this.#bytes.byteLength * 8) {
116
+ if (this.read(1) !== 0) throw new ArchiveError("Invalid ARJ method-4 compressed data: non-zero trailing bits");
117
+ }
118
+ }
119
+ }
120
+
121
+ function decompressArjMethod4(packed: Uint8Array, outSize: number): Uint8Array {
122
+ const reader = new ArjBitReader(packed);
123
+ const output = new Uint8Array(outSize);
124
+ let outputPosition = 0;
125
+ while (outputPosition < outSize) {
126
+ let lengthCode = 0;
127
+ let lengthWidth = 0;
128
+ for (; lengthWidth < 7; lengthWidth++) {
129
+ if (reader.read(1) === 0) break;
130
+ lengthCode += 2 ** lengthWidth;
131
+ }
132
+ if (lengthWidth !== 0) lengthCode += reader.read(lengthWidth);
133
+ if (lengthCode === 0) {
134
+ output[outputPosition++] = reader.read(8);
135
+ continue;
136
+ }
137
+ const length = lengthCode + 2;
138
+ if (length > outSize - outputPosition) {
139
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: match exceeds declared size");
140
+ }
141
+ let positionCode = 0;
142
+ let positionWidth = 9;
143
+ for (; positionWidth < 13; positionWidth++) {
144
+ if (reader.read(1) === 0) break;
145
+ positionCode += 2 ** positionWidth;
146
+ }
147
+ positionCode += reader.read(positionWidth);
148
+ if (positionCode >= 26_624 || positionCode >= outputPosition) {
149
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: history distance is out of range");
150
+ }
151
+ let sourcePosition = outputPosition - positionCode - 1;
152
+ for (let index = 0; index < length; index++) output[outputPosition++] = output[sourcePosition++]!;
153
+ }
154
+ reader.assertZeroPadding();
155
+ return output;
156
+ }
157
+
158
+ class ArjMemberSource implements MemberSource {
159
+ readonly #archive: Uint8Array;
160
+ readonly #start: number;
161
+ readonly #packedSize: number;
162
+ readonly #method: number;
163
+ readonly #crc: number;
164
+
165
+ constructor(archive: Uint8Array, start: number, packedSize: number, method: number, crc: number) {
166
+ this.#archive = archive;
167
+ this.#start = start;
168
+ this.#packedSize = packedSize;
169
+ this.#method = method;
170
+ this.#crc = crc;
171
+ }
172
+
173
+ async read(size: number, memberPath: string): Promise<Uint8Array> {
174
+ const packed = this.#archive.subarray(this.#start, this.#start + this.#packedSize);
175
+ let output: Uint8Array;
176
+ switch (this.#method) {
177
+ case 0:
178
+ if (packed.byteLength !== size)
179
+ throw new ArchiveError(`ARJ member '${memberPath}' has inconsistent stored size`);
180
+ output = packed.slice();
181
+ break;
182
+ case 1:
183
+ case 2:
184
+ case 3:
185
+ output = decompressLhStatic(packed, size, 26_624, 5, 17, `ARJ method ${this.#method}`);
186
+ break;
187
+ case 4:
188
+ output = decompressArjMethod4(packed, size);
189
+ break;
190
+ case 8:
191
+ case 9:
192
+ if (size !== 0 || packed.byteLength !== 0) {
193
+ throw new ArchiveError(`ARJ member '${memberPath}' has invalid no-data method sizes`);
194
+ }
195
+ output = new Uint8Array(0);
196
+ break;
197
+ default:
198
+ throw new ArchiveError(`ARJ member '${memberPath}' uses unsupported compression method ${this.#method}`);
199
+ }
200
+ if (output.byteLength !== size)
201
+ throw new ArchiveError(`ARJ member '${memberPath}' extracted to an unexpected size`);
202
+ if (this.#method !== 8 && crc32(output) !== this.#crc) {
203
+ throw new ArchiveError(`ARJ member '${memberPath}' failed CRC32 verification`);
204
+ }
205
+ return output;
206
+ }
207
+ }
208
+
209
+ function normalizeHostPath(rawPath: string, hostOs: number): string {
210
+ let path = rawPath.replaceAll("\\", "/");
211
+ if (hostOs === 1) path = path.replaceAll(">", "/");
212
+ else if (hostOs === 4) path = path.replaceAll(":", "/");
213
+ return path;
214
+ }
215
+
216
+ /** Probe whether bytes begin with a CRC-framed ARJ main header. */
217
+ export function sniffArj(bytes: Uint8Array): boolean {
218
+ if (bytes.byteLength < 34 || bytes[0] !== ARJ_SIGNATURE_0 || bytes[1] !== ARJ_SIGNATURE_1) return false;
219
+ const size = u16(bytes, 2);
220
+ if (size < 30 || size > ARJ_MAX_BASIC_HEADER || bytes.byteLength < size + 8) return false;
221
+ const body = bytes.subarray(4, 4 + size);
222
+ return body[0]! >= 30 && body[0]! <= size && body[6] === 2 && crc32(body) === u32(bytes, 4 + size);
223
+ }
224
+
225
+ /** Index an ARJ archive and lazily decode stored, static-Huffman, and fast-LZSS members. */
226
+ export const readArj: FormatReader = async (
227
+ source: ByteSource,
228
+ options: FormatReadOptions,
229
+ ): Promise<ArchiveIndexEntry[]> => {
230
+ assertInMemorySize(source.size, options.limits);
231
+ let bytes: Uint8Array;
232
+ try {
233
+ bytes = await readAllBytes(source);
234
+ } catch (error) {
235
+ if (error instanceof ArchiveError) throw error;
236
+ throw new ArchiveError(`Unable to read ARJ archive: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ if (bytes.byteLength !== source.size) throw new ArchiveError("Invalid ARJ archive: truncated data");
239
+ if (!sniffArj(bytes)) throw new ArchiveError("Invalid ARJ archive header");
240
+
241
+ const main = parseArjBlock(bytes, 0, options);
242
+ if (main.isEnd) throw new ArchiveError("Invalid ARJ archive: missing main header");
243
+ const mainFirstHeaderSize = bytes[main.bodyStart]!;
244
+ if (mainFirstHeaderSize < 30 || mainFirstHeaderSize > main.bodySize || bytes[main.bodyStart + 6] !== 2) {
245
+ throw new ArchiveError("Invalid ARJ main header");
246
+ }
247
+ const mainFlags = bytes[main.bodyStart + 4]!;
248
+ if ((mainFlags & 0x01) !== 0) throw new ArchiveError("Encrypted ARJ archives are unsupported");
249
+ if ((mainFlags & 0x04) !== 0) throw new ArchiveError("Multi-volume ARJ archives are unsupported");
250
+
251
+ const entries: ArchiveIndexEntry[] = [];
252
+ let offset = main.nextOffset;
253
+ let metadataSize = main.metadataSize;
254
+ let parsedCount = 0;
255
+ for (;;) {
256
+ const block = parseArjBlock(bytes, offset, options);
257
+ metadataSize += block.metadataSize;
258
+ assertIndexSize(metadataSize, options.limits, "index");
259
+ if (block.isEnd) break;
260
+ assertEntryCount(++parsedCount, options.limits);
261
+ const firstHeaderSize = bytes[block.bodyStart]!;
262
+ if (firstHeaderSize < 30 || firstHeaderSize > block.bodySize) throw new ArchiveError("Invalid ARJ local header");
263
+ const hostOs = bytes[block.bodyStart + 3]!;
264
+ const flags = bytes[block.bodyStart + 4]!;
265
+ const method = bytes[block.bodyStart + 5]!;
266
+ const fileType = bytes[block.bodyStart + 6]!;
267
+ if ((flags & 0x01) !== 0) throw new ArchiveError("Encrypted ARJ members are unsupported");
268
+ if ((flags & 0x0c) !== 0) throw new ArchiveError("Multi-volume ARJ members are unsupported");
269
+ const packedSize = u32(bytes, block.bodyStart + 12);
270
+ const size = u32(bytes, block.bodyStart + 16);
271
+ const fileCrc = u32(bytes, block.bodyStart + 20);
272
+ const accessMode = u16(bytes, block.bodyStart + 26);
273
+ const filename = readCString(
274
+ bytes,
275
+ block.bodyStart + firstHeaderSize,
276
+ block.bodyStart + block.bodySize,
277
+ "filename",
278
+ );
279
+ readCString(bytes, filename.next, block.bodyStart + block.bodySize, "comment");
280
+ assertArchivePathBytes(
281
+ filename.next - (block.bodyStart + firstHeaderSize) - 1,
282
+ "member path",
283
+ options.limits.maxPathBytes,
284
+ );
285
+ const rawPath = normalizeHostPath(filename.value, hostOs);
286
+ assertArchivePathString(rawPath, "member path", options.limits.maxPathBytes);
287
+ assertArchiveMemberSize(size, rawPath || "<unnamed>", options.limits);
288
+ const dataStart = block.nextOffset;
289
+ assertRange(bytes, dataStart, dataStart + packedSize, "member data");
290
+ offset = dataStart + packedSize;
291
+ const path = normalizeArchiveEntryPath(rawPath);
292
+ if (!path) continue;
293
+ const isDirectory = fileType === 3;
294
+ let mode: number | undefined;
295
+ if ((hostOs === 2 || hostOs === 8) && accessMode !== 0) {
296
+ mode = (isDirectory ? 0x4000 : 0x8000) | (accessMode & 0x0fff);
297
+ }
298
+ const rawMtime = u32(bytes, block.bodyStart + 8);
299
+ const mtimeMs =
300
+ hostOs === 2 || hostOs === 8 ? (rawMtime === 0 ? undefined : rawMtime * 1000) : dosTimeToMs(rawMtime);
301
+ entries.push({
302
+ path,
303
+ isDirectory,
304
+ size: isDirectory ? 0 : size,
305
+ mtimeMs,
306
+ mode,
307
+ storage: isDirectory
308
+ ? undefined
309
+ : { type: "member", source: new ArjMemberSource(bytes, dataStart, packedSize, method, fileCrc) },
310
+ });
311
+ }
312
+ if (parsedCount === 0) throw new ArchiveError("Invalid ARJ archive: no members");
313
+ return entries;
314
+ };