@patrickdk77/archive-defuse 0.0.0-stage → 0.1.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 (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +378 -2
  3. package/dist/aes7z.d.ts +16 -0
  4. package/dist/aes7z.js +131 -0
  5. package/dist/alone.d.ts +21 -0
  6. package/dist/alone.js +65 -0
  7. package/dist/bcj2.d.ts +21 -0
  8. package/dist/bcj2.js +140 -0
  9. package/dist/bzip2.d.ts +21 -0
  10. package/dist/bzip2.js +688 -0
  11. package/dist/cab.d.ts +63 -0
  12. package/dist/cab.js +493 -0
  13. package/dist/checks.d.ts +23 -0
  14. package/dist/checks.js +123 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +364 -0
  17. package/dist/codecs.d.ts +39 -0
  18. package/dist/codecs.js +124 -0
  19. package/dist/coders.d.ts +58 -0
  20. package/dist/coders.js +392 -0
  21. package/dist/crc32.d.ts +8 -0
  22. package/dist/crc32.js +59 -0
  23. package/dist/detect.d.ts +10 -0
  24. package/dist/detect.js +95 -0
  25. package/dist/encoder.d.ts +44 -0
  26. package/dist/encoder.js +114 -0
  27. package/dist/engine.d.ts +44 -0
  28. package/dist/engine.js +753 -0
  29. package/dist/errors.d.ts +64 -0
  30. package/dist/errors.js +98 -0
  31. package/dist/filters.d.ts +34 -0
  32. package/dist/filters.js +385 -0
  33. package/dist/findings.d.ts +26 -0
  34. package/dist/findings.js +176 -0
  35. package/dist/formats.d.ts +24 -0
  36. package/dist/formats.js +106 -0
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.js +53 -0
  39. package/dist/index.mjs +3 -0
  40. package/dist/inflate64.d.ts +6 -0
  41. package/dist/inflate64.js +388 -0
  42. package/dist/io.d.ts +78 -0
  43. package/dist/io.js +379 -0
  44. package/dist/lzma-formats.d.ts +75 -0
  45. package/dist/lzma-formats.js +373 -0
  46. package/dist/lzma-sniff.d.ts +20 -0
  47. package/dist/lzma-sniff.js +73 -0
  48. package/dist/lzma-writers.d.ts +35 -0
  49. package/dist/lzma-writers.js +276 -0
  50. package/dist/lzma1/LICENSE +201 -0
  51. package/dist/lzma1/NOTICE +36 -0
  52. package/dist/lzma1/crc32.d.ts +4 -0
  53. package/dist/lzma1/crc32.js +19 -0
  54. package/dist/lzma1/hash234.d.ts +22 -0
  55. package/dist/lzma1/hash234.js +75 -0
  56. package/dist/lzma1/hc4.d.ts +30 -0
  57. package/dist/lzma1/hc4.js +142 -0
  58. package/dist/lzma1/header.d.ts +32 -0
  59. package/dist/lzma1/header.js +73 -0
  60. package/dist/lzma1/lz-decoder.d.ts +41 -0
  61. package/dist/lzma1/lz-decoder.js +128 -0
  62. package/dist/lzma1/lz-encoder.d.ts +87 -0
  63. package/dist/lzma1/lz-encoder.js +166 -0
  64. package/dist/lzma1/lzma-coder.d.ts +107 -0
  65. package/dist/lzma1/lzma-coder.js +162 -0
  66. package/dist/lzma1/lzma-decoder.d.ts +36 -0
  67. package/dist/lzma1/lzma-decoder.js +484 -0
  68. package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
  69. package/dist/lzma1/lzma-encoder-fast.js +114 -0
  70. package/dist/lzma1/lzma-encoder.d.ts +105 -0
  71. package/dist/lzma1/lzma-encoder.js +497 -0
  72. package/dist/lzma1/lzma2-decoder.d.ts +85 -0
  73. package/dist/lzma1/lzma2-decoder.js +249 -0
  74. package/dist/lzma1/range-coder.d.ts +18 -0
  75. package/dist/lzma1/range-coder.js +23 -0
  76. package/dist/lzma1/range-decoder.d.ts +41 -0
  77. package/dist/lzma1/range-decoder.js +78 -0
  78. package/dist/lzma1/range-encoder.d.ts +53 -0
  79. package/dist/lzma1/range-encoder.js +269 -0
  80. package/dist/lzma1-stream.d.ts +60 -0
  81. package/dist/lzma1-stream.js +202 -0
  82. package/dist/model.d.ts +141 -0
  83. package/dist/model.js +16 -0
  84. package/dist/names.d.ts +13 -0
  85. package/dist/names.js +66 -0
  86. package/dist/pack.d.ts +79 -0
  87. package/dist/pack.js +590 -0
  88. package/dist/plugins.d.ts +13 -0
  89. package/dist/plugins.js +56 -0
  90. package/dist/rules.d.ts +25 -0
  91. package/dist/rules.js +403 -0
  92. package/dist/score.d.ts +17 -0
  93. package/dist/score.js +58 -0
  94. package/dist/seven.d.ts +75 -0
  95. package/dist/seven.js +492 -0
  96. package/dist/sevenzip.d.ts +133 -0
  97. package/dist/sevenzip.js +529 -0
  98. package/dist/single.d.ts +103 -0
  99. package/dist/single.js +453 -0
  100. package/dist/sniff.d.ts +20 -0
  101. package/dist/sniff.js +224 -0
  102. package/dist/stream-meter.d.ts +39 -0
  103. package/dist/stream-meter.js +54 -0
  104. package/dist/tar.d.ts +102 -0
  105. package/dist/tar.js +765 -0
  106. package/dist/types.d.ts +311 -0
  107. package/dist/types.js +133 -0
  108. package/dist/unpack.d.ts +67 -0
  109. package/dist/unpack.js +194 -0
  110. package/dist/xz.d.ts +25 -0
  111. package/dist/xz.js +333 -0
  112. package/dist/zip-lzma.d.ts +15 -0
  113. package/dist/zip-lzma.js +137 -0
  114. package/dist/zip-writer.d.ts +51 -0
  115. package/dist/zip-writer.js +305 -0
  116. package/dist/zip.d.ts +99 -0
  117. package/dist/zip.js +901 -0
  118. package/dist/zipcrypt.d.ts +47 -0
  119. package/dist/zipcrypt.js +233 -0
  120. package/dist/zstd.d.ts +25 -0
  121. package/dist/zstd.js +160 -0
  122. package/package.json +94 -4
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.zipLzma = zipLzma;
4
+ exports.zipXz = zipXz;
5
+ const coders_1 = require("./coders");
6
+ const errors_1 = require("./errors");
7
+ const io_1 = require("./io");
8
+ const lzma1_stream_1 = require("./lzma1-stream");
9
+ const types_1 = require("./types");
10
+ const xz_1 = require("./xz");
11
+ /** Input handed to the LZMA decoder per step, so one step decodes a bounded amount. */
12
+ const SLICE = 1024;
13
+ /** APPNOTE 5.8.8: two version bytes, a two-byte properties size, then the five bytes of LZMA properties. */
14
+ const HEADER = 9;
15
+ const concat = (a, b) => {
16
+ const out = new Uint8Array(a.length + b.length);
17
+ out.set(a, 0);
18
+ out.set(b, a.length);
19
+ return out;
20
+ };
21
+ /**
22
+ * Decodes a method 14 entry: the LZMA header APPNOTE 5.8.8 gives, then raw LZMA data of the entry's size, which may
23
+ * end with an end marker. `consumed.bytes` follows the input the data took, so the caller can see bytes after it.
24
+ */
25
+ async function* zipLzma(chunks, size, consumed, dictionaryBytes) {
26
+ const it = chunks[Symbol.asyncIterator]();
27
+ let head = new Uint8Array(0);
28
+ try {
29
+ while (head.length < HEADER) {
30
+ const r = await it.next();
31
+ if (r.done)
32
+ throw new errors_1.DataError('the LZMA header is cut short', true);
33
+ head = concat(head, r.value);
34
+ }
35
+ const propsSize = head[2] | (head[3] << 8);
36
+ if (propsSize !== 5)
37
+ throw new errors_1.DataError(`LZMA properties of ${propsSize} bytes, not 5`);
38
+ const dict = (head[5] | (head[6] << 8) | (head[7] << 16) | (head[8] << 24)) >>> 0;
39
+ const need = (0, lzma1_stream_1.effectiveDictSize)(dict, size);
40
+ if (need > dictionaryBytes)
41
+ throw new errors_1.StopError(types_1.ArchiveCategory.Limit, types_1.ArchiveDetail.DictionarySize, 'Dictionary size limit exceeded', { limit: dictionaryBytes, dictionary: need });
42
+ const out = [];
43
+ let stream;
44
+ try {
45
+ stream = new lzma1_stream_1.Lzma1Stream((0, lzma1_stream_1.decodeProps)(head[4], dict), size, c => out.push(c.slice()), true);
46
+ }
47
+ catch (e) {
48
+ throw (0, coders_1.lzmaError)(e);
49
+ }
50
+ let fed = HEADER;
51
+ consumed.bytes = HEADER;
52
+ let rest = head.subarray(HEADER);
53
+ for (;;) {
54
+ for (let p = 0; p < rest.length; p += SLICE) {
55
+ const piece = rest.subarray(p, p + SLICE);
56
+ try {
57
+ stream.write(piece);
58
+ }
59
+ catch (e) {
60
+ throw (0, coders_1.lzmaError)(e);
61
+ }
62
+ fed += piece.length;
63
+ if (!stream.done)
64
+ consumed.bytes = fed;
65
+ yield* out.splice(0);
66
+ }
67
+ const r = await it.next();
68
+ if (r.done)
69
+ break;
70
+ rest = r.value;
71
+ }
72
+ try {
73
+ stream.end();
74
+ }
75
+ catch (e) {
76
+ throw (0, coders_1.lzmaError)(e);
77
+ }
78
+ yield* out.splice(0);
79
+ consumed.bytes = HEADER + stream.consumed;
80
+ }
81
+ finally {
82
+ await it.return?.();
83
+ }
84
+ }
85
+ /** The engine's meter in the shape the xz reader counts through. */
86
+ function counter(meter) {
87
+ let decoded = 0;
88
+ let compressed = 0;
89
+ return {
90
+ get decoded() {
91
+ return decoded;
92
+ },
93
+ get compressed() {
94
+ return compressed;
95
+ },
96
+ tick: () => meter.time(),
97
+ packed: n => {
98
+ compressed += n;
99
+ },
100
+ add: n => {
101
+ decoded += n;
102
+ meter.add(decoded, compressed);
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Decodes a method 95 entry, which holds a whole xz file. The xz reader reads by offset, so the entry's compressed
108
+ * bytes go to memory or a temporary file first. A file that goes on past its streams, or that has notes the xz reader
109
+ * makes, such as bad padding, does not decode: an entry is one xz file and nothing else.
110
+ */
111
+ async function* zipXz(chunks, consumed, meter, temp, threshold, limits) {
112
+ const spill = new io_1.SpillSink(temp, threshold);
113
+ try {
114
+ for await (const c of chunks) {
115
+ meter.time();
116
+ await spill.write(c);
117
+ }
118
+ await spill.close();
119
+ const source = spill.source();
120
+ try {
121
+ const info = { streams: 0, checks: [], notes: [] };
122
+ yield* (0, xz_1.readXz)({ reader: new io_1.Reader(source, spill.length), size: spill.length, limits, meter: counter(meter) }, info);
123
+ const end = info.trailingAt ?? spill.length;
124
+ consumed.bytes = end;
125
+ // Padding between streams and a stream with no check are well formed; the entry's CRC covers the data.
126
+ const faults = info.notes.filter(n => n.detail !== types_1.ArchiveDetail.StreamPadding && n.detail !== types_1.ArchiveDetail.NoCheck);
127
+ if (faults.length)
128
+ throw new errors_1.DataError(`the xz data is not well formed: ${faults.map(n => `${n.category}/${n.detail}`).join(', ')}`);
129
+ }
130
+ finally {
131
+ await source.close?.();
132
+ }
133
+ }
134
+ finally {
135
+ await spill.dispose();
136
+ }
137
+ }
@@ -0,0 +1,51 @@
1
+ import { type WriteContext, type WritePlan } from './model';
2
+ import type { ByteSink } from './types';
3
+ import type { ZipArchive, ZipEntry } from './zip';
4
+ /** Extra field records that hold only times and owners. The rewrite keeps them unless metadata is stripped. */
5
+ export declare const METADATA_EXTRAS: Set<number>;
6
+ /** What the headers of one written entry say. */
7
+ export interface Written {
8
+ name: Uint8Array;
9
+ flags: number;
10
+ method: number;
11
+ versionNeeded: number;
12
+ versionMadeBy: number;
13
+ externalAttr: number;
14
+ internalAttr: number;
15
+ time: number;
16
+ date: number;
17
+ crc: number;
18
+ compressedSize: number;
19
+ size: number;
20
+ extras: Uint8Array;
21
+ comment: Uint8Array;
22
+ }
23
+ /**
24
+ * Writes the entries of a ZIP archive one after another, then its central directory. Local headers carry the CRC and
25
+ * sizes, so no entry needs a data descriptor. ZIP64 records appear only where a size, an offset or the entry count
26
+ * needs them.
27
+ */
28
+ export declare class ZipOutput {
29
+ private readonly sink;
30
+ private offset;
31
+ private readonly central;
32
+ constructor(sink: ByteSink);
33
+ /** Writes bytes as they are, such as data kept before the archive. */
34
+ put(b: Uint8Array): Promise<void>;
35
+ /** Writes one entry: its local header, then `chunks`, which must hold exactly `info.compressedSize` bytes. */
36
+ entry(info: Written, chunks: () => AsyncIterable<Uint8Array>, label: string): Promise<void>;
37
+ /**
38
+ * Writes one entry whose sizes are known only once its data has gone out: a local header with the data descriptor
39
+ * flag and no CRC or sizes, then `chunks`, the compressed data, then a data descriptor with the CRC and the sizes.
40
+ * `totals` gives the CRC and size of the uncompressed data once `chunks` has ended. An entry of 4 GiB or more gets
41
+ * eight-byte sizes in its descriptor and a ZIP64 field in the central directory, as archiver writes one.
42
+ */
43
+ streamEntry(info: Written, chunks: AsyncIterable<Uint8Array>, totals: () => {
44
+ crc: number;
45
+ size: number;
46
+ }): Promise<void>;
47
+ /** Writes the central directory and the end records, with the archive's comment. */
48
+ end(comment: Uint8Array): Promise<void>;
49
+ }
50
+ /** Writes `zip` again with the entries `plan` keeps. */
51
+ export declare function writeZip(zip: ZipArchive, plan: WritePlan<ZipEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZipOutput = exports.METADATA_EXTRAS = void 0;
4
+ exports.writeZip = writeZip;
5
+ // Copied from office-defuse src/zip-writer.ts. Changed: an entry keeps its method, times, permissions, comment and the
6
+ // extra fields the plan keeps, and either copies its compressed bytes without their encryption or is compressed again
7
+ // from the content the plan gives. Data the plan keeps before and after the archive is written around it.
8
+ const codecs_1 = require("./codecs");
9
+ const crc32_1 = require("./crc32");
10
+ const io_1 = require("./io");
11
+ const model_1 = require("./model");
12
+ /** Extra field records that hold only times and owners. The rewrite keeps them unless metadata is stripped. */
13
+ exports.METADATA_EXTRAS = new Set([0x5455, 0x000a, 0x7875, 0x5855, 0x7855]);
14
+ const MAX16 = 0xffff;
15
+ const MAX32 = 0xffffffff;
16
+ /** 1980-01-01 00:00, the earliest date a ZIP header holds, for entries whose times are stripped. */
17
+ const DOS_DATE = (0 << 9) | (1 << 5) | 1;
18
+ /** Unix file type bits, for the attributes of an entry that has permissions. */
19
+ const TYPE_BITS = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
20
+ class Builder {
21
+ parts = [];
22
+ u16(v) {
23
+ this.parts.push(v & 0xff, (v >>> 8) & 0xff);
24
+ return this;
25
+ }
26
+ u32(v) {
27
+ return this.u16(v & 0xffff).u16(Math.floor(v / 0x10000) & 0xffff);
28
+ }
29
+ u64(v) {
30
+ return this.u32(v % 0x100000000).u32(Math.floor(v / 0x100000000));
31
+ }
32
+ bytes(b) {
33
+ for (const x of b)
34
+ this.parts.push(x);
35
+ return this;
36
+ }
37
+ build() {
38
+ return Uint8Array.from(this.parts);
39
+ }
40
+ }
41
+ const versionFor = (method) => (method === 93 || method === 14 || method === 95 ? 63 : method === 12 ? 46 : method === 9 ? 21 : 20);
42
+ /**
43
+ * Writes the entries of a ZIP archive one after another, then its central directory. Local headers carry the CRC and
44
+ * sizes, so no entry needs a data descriptor. ZIP64 records appear only where a size, an offset or the entry count
45
+ * needs them.
46
+ */
47
+ class ZipOutput {
48
+ sink;
49
+ offset = 0;
50
+ central = [];
51
+ constructor(sink) {
52
+ this.sink = sink;
53
+ }
54
+ /** Writes bytes as they are, such as data kept before the archive. */
55
+ async put(b) {
56
+ await this.sink.write(b);
57
+ this.offset += b.length;
58
+ }
59
+ /** Writes one entry: its local header, then `chunks`, which must hold exactly `info.compressedSize` bytes. */
60
+ async entry(info, chunks, label) {
61
+ const big = info.size >= MAX32 || info.compressedSize >= MAX32;
62
+ const localOffset = this.offset;
63
+ const local = new Builder()
64
+ .u32(0x04034b50)
65
+ .u16(big ? Math.max(45, info.versionNeeded) : info.versionNeeded)
66
+ .u16(info.flags)
67
+ .u16(info.method)
68
+ .u16(info.time)
69
+ .u16(info.date)
70
+ .u32(info.crc)
71
+ .u32(big ? MAX32 : info.compressedSize)
72
+ .u32(big ? MAX32 : info.size)
73
+ .u16(info.name.length)
74
+ .u16(info.extras.length + (big ? 20 : 0))
75
+ .bytes(info.name);
76
+ if (big)
77
+ local.u16(0x0001).u16(16).u64(info.size).u64(info.compressedSize);
78
+ local.bytes(info.extras);
79
+ await this.put(local.build());
80
+ let written = 0;
81
+ for await (const c of chunks()) {
82
+ written += c.length;
83
+ await this.put(c);
84
+ }
85
+ if (written !== info.compressedSize)
86
+ throw new Error(`Entry ${label} has ${written} compressed bytes, not ${info.compressedSize}`);
87
+ this.central.push(centralRecord(info, localOffset));
88
+ }
89
+ /**
90
+ * Writes one entry whose sizes are known only once its data has gone out: a local header with the data descriptor
91
+ * flag and no CRC or sizes, then `chunks`, the compressed data, then a data descriptor with the CRC and the sizes.
92
+ * `totals` gives the CRC and size of the uncompressed data once `chunks` has ended. An entry of 4 GiB or more gets
93
+ * eight-byte sizes in its descriptor and a ZIP64 field in the central directory, as archiver writes one.
94
+ */
95
+ async streamEntry(info, chunks, totals) {
96
+ const flags = info.flags | 0x0008;
97
+ const localOffset = this.offset;
98
+ const local = new Builder()
99
+ .u32(0x04034b50)
100
+ .u16(info.versionNeeded)
101
+ .u16(flags)
102
+ .u16(info.method)
103
+ .u16(info.time)
104
+ .u16(info.date)
105
+ .u32(0)
106
+ .u32(0)
107
+ .u32(0)
108
+ .u16(info.name.length)
109
+ .u16(info.extras.length)
110
+ .bytes(info.name)
111
+ .bytes(info.extras);
112
+ await this.put(local.build());
113
+ let compressedSize = 0;
114
+ for await (const c of chunks) {
115
+ compressedSize += c.length;
116
+ await this.put(c);
117
+ }
118
+ const { crc, size } = totals();
119
+ const descriptor = new Builder().u32(0x08074b50).u32(crc);
120
+ if (size >= MAX32 || compressedSize >= MAX32)
121
+ descriptor.u64(compressedSize).u64(size);
122
+ else
123
+ descriptor.u32(compressedSize).u32(size);
124
+ await this.put(descriptor.build());
125
+ this.central.push(centralRecord({ ...info, flags, crc, size, compressedSize }, localOffset));
126
+ }
127
+ /** Writes the central directory and the end records, with the archive's comment. */
128
+ async end(comment) {
129
+ const dirOffset = this.offset;
130
+ for (const c of this.central)
131
+ await this.put(c);
132
+ const dirSize = this.offset - dirOffset;
133
+ const count = this.central.length;
134
+ const big = count >= MAX16 || dirOffset >= MAX32 || dirSize >= MAX32;
135
+ if (big) {
136
+ const end64 = this.offset;
137
+ await this.put(new Builder().u32(0x06064b50).u64(44).u16(45).u16(45).u32(0).u32(0).u64(count).u64(count).u64(dirSize).u64(dirOffset).build());
138
+ await this.put(new Builder().u32(0x07064b50).u32(0).u64(end64).u32(1).build());
139
+ }
140
+ await this.put(new Builder()
141
+ .u32(0x06054b50)
142
+ .u16(0)
143
+ .u16(0)
144
+ .u16(big ? MAX16 : count)
145
+ .u16(big ? MAX16 : count)
146
+ .u32(big ? MAX32 : dirSize)
147
+ .u32(big ? MAX32 : dirOffset)
148
+ .u16(comment.length)
149
+ .bytes(comment)
150
+ .build());
151
+ }
152
+ }
153
+ exports.ZipOutput = ZipOutput;
154
+ /** The central directory record of an entry whose local header starts at `localOffset`. */
155
+ function centralRecord(info, localOffset) {
156
+ const far = localOffset >= MAX32;
157
+ const z64 = new Builder();
158
+ if (info.size >= MAX32)
159
+ z64.u64(info.size);
160
+ if (info.compressedSize >= MAX32)
161
+ z64.u64(info.compressedSize);
162
+ if (far)
163
+ z64.u64(localOffset);
164
+ const z64Bytes = z64.build();
165
+ const needed = z64Bytes.length ? Math.max(45, info.versionNeeded) : info.versionNeeded;
166
+ const c = new Builder()
167
+ .u32(0x02014b50)
168
+ .u16(info.versionMadeBy)
169
+ .u16(needed)
170
+ .u16(info.flags)
171
+ .u16(info.method)
172
+ .u16(info.time)
173
+ .u16(info.date)
174
+ .u32(info.crc)
175
+ .u32(info.compressedSize >= MAX32 ? MAX32 : info.compressedSize)
176
+ .u32(info.size >= MAX32 ? MAX32 : info.size)
177
+ .u16(info.name.length)
178
+ .u16(info.extras.length + (z64Bytes.length ? z64Bytes.length + 4 : 0))
179
+ .u16(info.comment.length)
180
+ .u16(0)
181
+ .u16(info.internalAttr)
182
+ .u32(info.externalAttr)
183
+ .u32(far ? MAX32 : localOffset)
184
+ .bytes(info.name);
185
+ if (z64Bytes.length)
186
+ c.u16(0x0001).u16(z64Bytes.length).bytes(z64Bytes);
187
+ c.bytes(info.extras).bytes(info.comment);
188
+ return c.build();
189
+ }
190
+ /** Writes `zip` again with the entries `plan` keeps. */
191
+ async function writeZip(zip, plan, sink, context) {
192
+ const out = new ZipOutput(sink);
193
+ if (plan.keepLeading)
194
+ for await (const c of zip.leading())
195
+ await out.put(c);
196
+ for (const k of plan.kept) {
197
+ context.meter().time();
198
+ const p = await prepare(zip, k, plan, context);
199
+ try {
200
+ await out.entry(p.info, p.chunks, k.name);
201
+ }
202
+ finally {
203
+ await p.spill?.dispose();
204
+ }
205
+ }
206
+ await out.end(plan.stripMetadata ? new Uint8Array(0) : zip.commentBytes);
207
+ if (plan.keepTrailing)
208
+ for await (const c of zip.trailing())
209
+ await out.put(c);
210
+ }
211
+ /**
212
+ * An extended timestamp record with its flags naming only the times it holds. The rewrite writes the central
213
+ * directory's record, which may hold the modification time alone, into the local header too, where the flags have to
214
+ * match the times that follow.
215
+ */
216
+ function timestampFlags(data) {
217
+ let count = (data.length - 1) / 4;
218
+ let flags = 0;
219
+ for (let bit = 1; bit <= 4 && count > 0; bit <<= 1) {
220
+ if (data[0] & bit) {
221
+ flags |= bit;
222
+ count--;
223
+ }
224
+ }
225
+ if (flags === data[0])
226
+ return data;
227
+ const out = Uint8Array.from(data);
228
+ out[0] = flags;
229
+ return out;
230
+ }
231
+ /** The headers and data of one kept entry: its own compressed bytes without their encryption, or its content compressed again. */
232
+ async function prepare(zip, k, plan, context) {
233
+ const e = k.entry;
234
+ const isDir = e.kind === 'directory';
235
+ const name = Buffer.from(isDir && !k.name.endsWith('/') ? `${k.name}/` : k.name, 'utf8');
236
+ const comment = plan.stripMetadata || !e.comment ? Buffer.alloc(0) : Buffer.from(e.comment, 'utf8');
237
+ const flags = /^[\x20-\x7e]*$/.test(k.name) && /^[\x20-\x7e]*$/.test(e.comment && !plan.stripMetadata ? e.comment : '') ? 0 : 0x0800;
238
+ const extras = Buffer.concat(e.extras
239
+ .filter(r => (exports.METADATA_EXTRAS.has(r.id) ? !plan.stripMetadata && (!r.malformed || plan.keepExtraFields) : plan.keepExtraFields))
240
+ .map(r => new Builder()
241
+ .u16(r.id)
242
+ .u16(r.data.length)
243
+ .bytes(r.id === 0x5455 && !r.malformed ? timestampFlags(r.data) : r.data)
244
+ .build()));
245
+ const unix = k.mode !== undefined;
246
+ const typeBits = TYPE_BITS[e.kind] ?? 0;
247
+ const info = {
248
+ name,
249
+ flags,
250
+ method: 0,
251
+ versionNeeded: 20,
252
+ versionMadeBy: unix ? (3 << 8) | 30 : 20,
253
+ externalAttr: unix ? (((typeBits | (k.mode ?? 0)) << 16) >>> 0) | (isDir ? 0x10 : 0) : (e.externalAttr & 0xff & ~0x10) | (isDir ? 0x10 : 0),
254
+ internalAttr: e.internalAttr,
255
+ time: plan.stripMetadata ? 0 : e.dosTime,
256
+ date: plan.stripMetadata ? DOS_DATE : e.dosDate,
257
+ crc: 0,
258
+ compressedSize: 0,
259
+ size: 0,
260
+ extras,
261
+ comment,
262
+ };
263
+ if (isDir)
264
+ return { info, chunks: model_1.nothing };
265
+ if (!k.replacement && !k.recompress) {
266
+ // The compressed bytes are copied, without any encryption they had.
267
+ info.method = e.methodCode;
268
+ info.versionNeeded = versionFor(e.methodCode);
269
+ info.crc = k.crc ?? e.crc;
270
+ info.size = e.size;
271
+ info.compressedSize = zip.plainLength(e);
272
+ return { info, chunks: () => zip.plain(e, k.password) };
273
+ }
274
+ // Compressed again: deflate, or stored when deflate gains nothing or would pass the run's ratio limit.
275
+ const size = (0, model_1.sizeOf)(k);
276
+ let crc = 0;
277
+ async function* withCrc(chunks) {
278
+ for await (const c of chunks) {
279
+ context.meter().time();
280
+ crc = (0, crc32_1.crc32)(c, crc);
281
+ yield c;
282
+ }
283
+ }
284
+ const spill = new io_1.SpillSink(context.temp, context.threshold);
285
+ try {
286
+ for await (const c of (0, codecs_1.deflateRaw)(withCrc((0, model_1.contentOf)(zip, k, context))))
287
+ await spill.write(c);
288
+ await spill.close();
289
+ }
290
+ catch (err) {
291
+ await spill.dispose();
292
+ throw err;
293
+ }
294
+ info.crc = crc;
295
+ info.size = size;
296
+ const passesRatio = size <= 1024 * 1024 || size <= spill.length * plan.ratioLimit;
297
+ if (size > 0 && spill.length < size && passesRatio) {
298
+ info.method = 8;
299
+ info.compressedSize = spill.length;
300
+ return { info, chunks: () => spill.read(), spill };
301
+ }
302
+ await spill.dispose();
303
+ info.compressedSize = size;
304
+ return { info, chunks: () => (0, model_1.contentOf)(zip, k, context) };
305
+ }
package/dist/zip.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { TempDir } from './io';
2
+ import type { Limits } from './stream-meter';
3
+ import { Reader } from './io';
4
+ import type { Archive, Entry, Meter, Note, WriteContext, WritePlan } from './model';
5
+ import { type ByteSink, type ByteSource } from './types';
6
+ export interface ZipLimits {
7
+ entries: number;
8
+ decompressedBytes: number;
9
+ totalDecompressedBytes: number;
10
+ compressionRatio: number;
11
+ }
12
+ /** One record of an extra field. */
13
+ export interface ExtraRecord {
14
+ id: number;
15
+ data: Uint8Array;
16
+ /** A times or owners record that does not have its documented layout. */
17
+ malformed?: boolean;
18
+ }
19
+ export interface ZipEntry extends Entry {
20
+ rawName: Uint8Array;
21
+ flags: number;
22
+ /** The method field of the headers: 99 for AES. */
23
+ storedMethod: number;
24
+ /** The method the data is compressed with, inside any encryption. */
25
+ methodCode: number;
26
+ versionMadeBy: number;
27
+ externalAttr: number;
28
+ internalAttr: number;
29
+ dosTime: number;
30
+ dosDate: number;
31
+ crc: number;
32
+ localOffset: number;
33
+ dataOffset: number;
34
+ /** Where the entry ends, after any data descriptor. */
35
+ end: number;
36
+ /** Central directory extra records, apart from the ones the rewrite replaces. */
37
+ extras: ExtraRecord[];
38
+ /** The entry's comment as stored. */
39
+ commentBytes: Uint8Array;
40
+ /** WinZip AES: 1 for AE-1, which keeps the CRC, 2 for AE-2, which does not. */
41
+ aesVersion?: number;
42
+ aesStrength?: number;
43
+ }
44
+ /**
45
+ * A ZIP archive read from its central directory, with every local header checked against it. Reading an entry
46
+ * decrypts and decodes it in chunks and checks its size and CRC.
47
+ */
48
+ export declare class ZipArchive implements Archive<ZipEntry> {
49
+ readonly reader: Reader;
50
+ readonly entries: ZipEntry[];
51
+ readonly notes: Note[];
52
+ /** Where the archive starts in the file: the first local header, or the directory when there is none. */
53
+ readonly start: number;
54
+ /** Where the archive ends: after the end record's comment. */
55
+ readonly end: number;
56
+ readonly commentBytes: Uint8Array;
57
+ /** Where the other end records that lead to a directory sit, which an entry's data can hold. */
58
+ readonly rivals: number[];
59
+ readonly format = "zip";
60
+ readonly single = false;
61
+ readonly lateNotes: Note[];
62
+ comment?: string;
63
+ private constructor();
64
+ /** What LZMA and XZ entries need from the run: its limits, and a temporary folder for an XZ entry's bytes. */
65
+ decoders?: {
66
+ temp: TempDir;
67
+ threshold: number;
68
+ limits: Limits;
69
+ };
70
+ static open(source: ByteSource, limits: ZipLimits, deadline?: number): Promise<ZipArchive>;
71
+ /** One entry from its central directory header. */
72
+ private static central;
73
+ /** The encryption an entry uses, from its flags and its AES field, which also gives the method inside. */
74
+ private static encryption;
75
+ /** Checks a local header and any data descriptor against the central directory, and fills in where the entry's data and the entry end. */
76
+ private static checkLocal;
77
+ /** Bytes before the first entry, bytes between entries and the directory that belong to none, and entries that share bytes. */
78
+ private static coverage;
79
+ hasMetadata(): boolean;
80
+ /** Reads a symbolic link's target, which a ZIP stores as the entry's content. */
81
+ linkTarget(e: ZipEntry, meter: Meter): Promise<string | undefined>;
82
+ /** The stored bytes of an entry. */
83
+ raw(e: ZipEntry): AsyncGenerator<Uint8Array>;
84
+ /** The bytes before the archive and after it. */
85
+ leading(): AsyncGenerator<Uint8Array>;
86
+ trailing(): AsyncGenerator<Uint8Array>;
87
+ /** The 12-byte ZipCrypto header's check byte: the CRC's high byte, or the time's with a data descriptor. */
88
+ private checkByte;
89
+ unlock(e: ZipEntry, password: string): Promise<boolean>;
90
+ /** The entry's compressed bytes without their encryption, for a rewrite that copies them. Their length comes from `plainLength`. */
91
+ plain(e: ZipEntry, password?: string): AsyncGenerator<Uint8Array>;
92
+ /** The length of what plain() yields. */
93
+ plainLength(e: ZipEntry): number;
94
+ /** The entry's content in chunks. Its size, its CRC unless AE-2 leaves it out, and the compressed bytes used must all agree with the central directory. */
95
+ read(e: ZipEntry, meter: Meter, password?: string): AsyncGenerator<Uint8Array>;
96
+ write(plan: WritePlan<ZipEntry>, sink: ByteSink, context: WriteContext): Promise<void>;
97
+ }
98
+ /** True when the file ends in what may be a ZIP archive: an end record signature in the last 64 KiB. */
99
+ export declare function hasZipEnd(source: ByteSource): Promise<boolean>;