@poe-platform/safe-bash 0.1.656 → 0.1.658

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 (129) hide show
  1. package/dist/safe-bash/chunks/{chunk-43HWIHXB.js → chunk-Z4UKD5FF.js} +192 -61
  2. package/dist/safe-bash/chunks/chunk-Z4UKD5FF.js.map +7 -0
  3. package/dist/safe-bash/chunks/{xz-U4QDTPZL.js → xz-4V5FXSPG.js} +555 -514
  4. package/dist/safe-bash/chunks/xz-4V5FXSPG.js.map +7 -0
  5. package/dist/safe-bash/commands/archive/index.d.ts +1 -1
  6. package/dist/safe-bash/commands/archive/index.d.ts.map +1 -1
  7. package/dist/safe-bash/commands/archive/internal.d.ts +32 -0
  8. package/dist/safe-bash/commands/archive/internal.d.ts.map +1 -1
  9. package/dist/safe-bash/commands/archive/internal.js.map +1 -1
  10. package/dist/safe-bash/commands/archive/unzip/arguments.d.ts +3 -0
  11. package/dist/safe-bash/commands/archive/unzip/arguments.d.ts.map +1 -1
  12. package/dist/safe-bash/commands/archive/unzip/arguments.js +31 -5
  13. package/dist/safe-bash/commands/archive/unzip/arguments.js.map +1 -1
  14. package/dist/safe-bash/commands/archive/unzip/safety.d.ts +4 -0
  15. package/dist/safe-bash/commands/archive/unzip/safety.d.ts.map +1 -1
  16. package/dist/safe-bash/commands/archive/unzip/safety.js +3 -0
  17. package/dist/safe-bash/commands/archive/unzip/safety.js.map +1 -1
  18. package/dist/safe-bash/commands/archive/unzip.d.ts.map +1 -1
  19. package/dist/safe-bash/commands/archive/unzip.js +159 -112
  20. package/dist/safe-bash/commands/archive/unzip.js.map +1 -1
  21. package/dist/safe-bash/commands/archive/zip/aes-primitives-unavailable.d.ts +2 -0
  22. package/dist/safe-bash/commands/archive/zip/aes-primitives-unavailable.d.ts.map +1 -0
  23. package/dist/safe-bash/commands/archive/zip/aes-primitives-unavailable.js +3 -0
  24. package/dist/safe-bash/commands/archive/zip/aes-primitives-unavailable.js.map +1 -0
  25. package/dist/safe-bash/commands/archive/zip/aes-primitives.d.ts +2 -0
  26. package/dist/safe-bash/commands/archive/zip/aes-primitives.d.ts.map +1 -0
  27. package/dist/safe-bash/commands/archive/zip/aes-primitives.js +3 -0
  28. package/dist/safe-bash/commands/archive/zip/aes-primitives.js.map +1 -0
  29. package/dist/safe-bash/commands/archive/zip/aes.d.ts +18 -0
  30. package/dist/safe-bash/commands/archive/zip/aes.d.ts.map +1 -0
  31. package/dist/safe-bash/commands/archive/zip/aes.js +157 -0
  32. package/dist/safe-bash/commands/archive/zip/aes.js.map +1 -0
  33. package/dist/safe-bash/commands/archive/zip/crypto.d.ts +16 -0
  34. package/dist/safe-bash/commands/archive/zip/crypto.d.ts.map +1 -1
  35. package/dist/safe-bash/commands/archive/zip/crypto.js +85 -0
  36. package/dist/safe-bash/commands/archive/zip/crypto.js.map +1 -1
  37. package/dist/safe-bash/commands/archive/zip/grow.d.ts +4 -0
  38. package/dist/safe-bash/commands/archive/zip/grow.d.ts.map +1 -0
  39. package/dist/safe-bash/commands/archive/zip/grow.js +3 -0
  40. package/dist/safe-bash/commands/archive/zip/grow.js.map +1 -0
  41. package/dist/safe-bash/commands/archive/zip/help.d.ts +4 -2
  42. package/dist/safe-bash/commands/archive/zip/help.d.ts.map +1 -1
  43. package/dist/safe-bash/commands/archive/zip/help.js +98 -3
  44. package/dist/safe-bash/commands/archive/zip/help.js.map +1 -1
  45. package/dist/safe-bash/commands/archive/zip/line-endings.d.ts +1 -0
  46. package/dist/safe-bash/commands/archive/zip/line-endings.d.ts.map +1 -1
  47. package/dist/safe-bash/commands/archive/zip/line-endings.js +158 -0
  48. package/dist/safe-bash/commands/archive/zip/line-endings.js.map +1 -1
  49. package/dist/safe-bash/commands/archive/zip/log.d.ts +23 -0
  50. package/dist/safe-bash/commands/archive/zip/log.d.ts.map +1 -0
  51. package/dist/safe-bash/commands/archive/zip/log.js +159 -0
  52. package/dist/safe-bash/commands/archive/zip/log.js.map +1 -0
  53. package/dist/safe-bash/commands/archive/zip/lzma.d.ts +10 -0
  54. package/dist/safe-bash/commands/archive/zip/lzma.d.ts.map +1 -0
  55. package/dist/safe-bash/commands/archive/zip/lzma.js +41 -0
  56. package/dist/safe-bash/commands/archive/zip/lzma.js.map +1 -0
  57. package/dist/safe-bash/commands/archive/zip/names.d.ts +3 -0
  58. package/dist/safe-bash/commands/archive/zip/names.d.ts.map +1 -0
  59. package/dist/safe-bash/commands/archive/zip/names.js +34 -0
  60. package/dist/safe-bash/commands/archive/zip/names.js.map +1 -0
  61. package/dist/safe-bash/commands/archive/zip/options.d.ts +7 -0
  62. package/dist/safe-bash/commands/archive/zip/options.d.ts.map +1 -1
  63. package/dist/safe-bash/commands/archive/zip/options.js +88 -17
  64. package/dist/safe-bash/commands/archive/zip/options.js.map +1 -1
  65. package/dist/safe-bash/commands/archive/zip/repair.d.ts +11 -0
  66. package/dist/safe-bash/commands/archive/zip/repair.d.ts.map +1 -0
  67. package/dist/safe-bash/commands/archive/zip/repair.js +392 -0
  68. package/dist/safe-bash/commands/archive/zip/repair.js.map +1 -0
  69. package/dist/safe-bash/commands/archive/zip/safety.d.ts +23 -4
  70. package/dist/safe-bash/commands/archive/zip/safety.d.ts.map +1 -1
  71. package/dist/safe-bash/commands/archive/zip/safety.js +73 -23
  72. package/dist/safe-bash/commands/archive/zip/safety.js.map +1 -1
  73. package/dist/safe-bash/commands/archive/zip/sfx.d.ts +5 -0
  74. package/dist/safe-bash/commands/archive/zip/sfx.d.ts.map +1 -0
  75. package/dist/safe-bash/commands/archive/zip/sfx.js +56 -0
  76. package/dist/safe-bash/commands/archive/zip/sfx.js.map +1 -0
  77. package/dist/safe-bash/commands/archive/zip/test-command.d.ts +6 -0
  78. package/dist/safe-bash/commands/archive/zip/test-command.d.ts.map +1 -0
  79. package/dist/safe-bash/commands/archive/zip/test-command.js +101 -0
  80. package/dist/safe-bash/commands/archive/zip/test-command.js.map +1 -0
  81. package/dist/safe-bash/commands/archive/zip/volumes.d.ts +22 -0
  82. package/dist/safe-bash/commands/archive/zip/volumes.d.ts.map +1 -0
  83. package/dist/safe-bash/commands/archive/zip/volumes.js +265 -0
  84. package/dist/safe-bash/commands/archive/zip/volumes.js.map +1 -0
  85. package/dist/safe-bash/commands/archive/zip/zip64.d.ts +20 -1
  86. package/dist/safe-bash/commands/archive/zip/zip64.d.ts.map +1 -1
  87. package/dist/safe-bash/commands/archive/zip/zip64.js +127 -17
  88. package/dist/safe-bash/commands/archive/zip/zip64.js.map +1 -1
  89. package/dist/safe-bash/commands/archive/zip-format.d.ts +30 -5
  90. package/dist/safe-bash/commands/archive/zip-format.d.ts.map +1 -1
  91. package/dist/safe-bash/commands/archive/zip-format.js +489 -91
  92. package/dist/safe-bash/commands/archive/zip-format.js.map +1 -1
  93. package/dist/safe-bash/commands/archive/zip.d.ts.map +1 -1
  94. package/dist/safe-bash/commands/archive/zip.js +729 -128
  95. package/dist/safe-bash/commands/archive/zip.js.map +1 -1
  96. package/dist/safe-bash/commands/bytes/compression/bounded-codec.d.ts +7 -0
  97. package/dist/safe-bash/commands/bytes/compression/bounded-codec.d.ts.map +1 -1
  98. package/dist/safe-bash/commands/bytes/compression/bounded-codec.js.map +1 -1
  99. package/dist/safe-bash/commands/bytes/compression/codec-loader.d.ts.map +1 -1
  100. package/dist/safe-bash/commands/bytes/compression/codec-loader.js +8 -1
  101. package/dist/safe-bash/commands/bytes/compression/codec-loader.js.map +1 -1
  102. package/dist/safe-bash/commands/bytes/compression/native/generated/xz.mjs +1 -1
  103. package/dist/safe-bash/commands/bytes/compression/native/sources.json +2 -2
  104. package/dist/safe-bash/commands/bytes/compression/native/types.d.ts +1 -0
  105. package/dist/safe-bash/commands/bytes/compression/native/types.d.ts.map +1 -1
  106. package/dist/safe-bash/commands/python/cache.d.ts +11 -0
  107. package/dist/safe-bash/commands/python/cache.d.ts.map +1 -0
  108. package/dist/safe-bash/commands/python/cache.js +41 -0
  109. package/dist/safe-bash/commands/python/cache.js.map +1 -0
  110. package/dist/safe-bash/commands/python/index.browser.js +7 -1
  111. package/dist/safe-bash/commands/python/index.d.ts +6 -2
  112. package/dist/safe-bash/commands/python/index.d.ts.map +1 -1
  113. package/dist/safe-bash/commands/python/index.js +16 -10
  114. package/dist/safe-bash/commands/python/index.js.map +1 -1
  115. package/dist/safe-bash/commands/python/manifest.d.ts +23 -0
  116. package/dist/safe-bash/commands/python/manifest.d.ts.map +1 -0
  117. package/dist/safe-bash/commands/python/manifest.js +48 -0
  118. package/dist/safe-bash/commands/python/manifest.js.map +1 -0
  119. package/dist/safe-bash/commands/python/provisioning.d.ts +17 -13
  120. package/dist/safe-bash/commands/python/provisioning.d.ts.map +1 -1
  121. package/dist/safe-bash/commands/python/provisioning.js +88 -46
  122. package/dist/safe-bash/commands/python/provisioning.js.map +1 -1
  123. package/dist/safe-bash/commands/text.js +1 -1
  124. package/dist/safe-bash/commands/text.js.map +1 -1
  125. package/dist/safe-bash/core.browser.js +3113 -718
  126. package/dist/safe-bash/core.browser.js.map +4 -4
  127. package/package.json +11 -3
  128. package/dist/safe-bash/chunks/chunk-43HWIHXB.js.map +0 -7
  129. package/dist/safe-bash/chunks/xz-U4QDTPZL.js.map +0 -7
@@ -1,10 +1,14 @@
1
- import { zip64Directory, zip64Fields, stripZip64, zip64Extra, writeZip64End } from "./zip/zip64.js";
1
+ import { zipGrowRecords } from "./zip/grow.js";
2
+ import { zip64Directory, zip64Fields, zipDiskOffset, zipDiskRecord, stripZip64, zip64Extra, zip64Member, zipEnd, zipDescriptor } from "./zip/zip64.js";
2
3
  import { crcTable } from "./zip/crc.js";
3
- import { collectBytes } from "../../contracts/index.js";
4
+ import { encryptZipPayload, decryptZipPayload } from "./zip/crypto.js";
5
+ import { aesParameters, aesExtra, encryptAesPayload, decryptAesPayload } from "./zip/aes.js";
6
+ import { collectBytes, readBytes } from "../../contracts/index.js";
4
7
  import { yieldTurn } from "../../contracts/yield.js";
5
8
  import { codec, CodecReader } from "../bytes/compression/codec.js";
6
9
  import { boundedCodec } from "../bytes/compression/bounded-codec.js";
7
10
  import { fail, text } from "./internal.js";
11
+ import { zipLzma } from "./zip/lzma.js";
8
12
  const encoder = new TextEncoder();
9
13
  const cp437 = "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ";
10
14
  export function crc32(bytes, previous = 0) {
@@ -19,7 +23,7 @@ function number(value, maximum, label) {
19
23
  }
20
24
  function admit(limits, signal) {
21
25
  signal.throwIfAborted();
22
- for (const key of ["maxArchiveBytes", "maxEntryBytes", "maxTotalBytes", "maxMembers", "maxPathBytes", "maxDepth", "maxPaxBytes", "maxTextBytes", "chunkSize"]) {
26
+ for (const key of ["maxArchiveBytes", "maxEntryBytes", "maxTotalBytes", "maxMembers", "maxPathBytes", "maxDepth", "maxPaxBytes", "maxTextBytes", "maxPatternSteps", "chunkSize"]) {
23
27
  number(limits[key], Number.MAX_SAFE_INTEGER, key);
24
28
  }
25
29
  if (limits.chunkSize < 512 || limits.chunkSize > 1024 * 1024)
@@ -29,6 +33,7 @@ function admit(limits, signal) {
29
33
  function pathBytes(name, limits) {
30
34
  if (name.length > Math.min(limits.maxPathBytes, 65535))
31
35
  fail("ZIP path byte limit exceeded");
36
+ number(Buffer.byteLength(name), Math.min(limits.maxPathBytes, limits.maxArchiveBytes, 65535), "path byte");
32
37
  const bytes = encoder.encode(name);
33
38
  if (!name || text(bytes) !== name || name.includes("\0"))
34
39
  fail("ZIP invalid or unsafe path");
@@ -72,7 +77,16 @@ function extras(bytes, rawName, comment, central, limits) {
72
77
  fail("ZIP64 invalid extra field length");
73
78
  result.zip64 = bytes.subarray(offset, offset + length);
74
79
  }
75
- if (identifier === 0x9901 || identifier === 0x0017 || identifier === 0x0018 || identifier === 0x0019)
80
+ if (identifier === 0x9901) {
81
+ if (length !== 7 || bytes[offset + 2] !== 65 || bytes[offset + 3] !== 69)
82
+ fail("ZIP invalid AES extra field");
83
+ const aes = { version: view.getUint16(offset, true), strength: (bytes[offset + 4] + 1) * 64, method: view.getUint16(offset + 5, true) };
84
+ aesParameters(aes);
85
+ if (aes.method !== 0 && aes.method !== 8 && aes.method !== 12)
86
+ fail("ZIP unsupported AES compression method");
87
+ result.aes = aes;
88
+ }
89
+ if (identifier === 0x0017 || identifier === 0x0018 || identifier === 0x0019)
76
90
  fail("ZIP encryption is unsupported");
77
91
  if (identifier === 0x7075 || identifier === 0x6375) {
78
92
  if (length < 5 || bytes[offset] !== 1)
@@ -114,13 +128,24 @@ function nameFrom(rawName, flags, metadata, limits) {
114
128
  }
115
129
  function format(method, flags, version) {
116
130
  number(flags, 65535, "general purpose flags");
117
- if (flags & (1 | 64 | 0x2000))
131
+ if (flags & (64 | 0x2000))
118
132
  fail("ZIP encryption is unsupported");
119
- if (flags & ~0x80e || method !== 8 && flags & 6)
133
+ if (method === 99) {
134
+ if (!(flags & 1) || flags & ~0x80f || version !== 51)
135
+ fail("ZIP invalid AES flags or extraction version");
136
+ return;
137
+ }
138
+ if (method === 14 ? flags & ~0x80b : flags & ~0x80f || method !== 8 && flags & 6)
120
139
  fail("ZIP unsupported general purpose flags");
140
+ if (method === 14) {
141
+ if (version !== 63)
142
+ fail("ZIP unsupported LZMA extraction version");
143
+ return;
144
+ }
121
145
  if (method !== 0 && method !== 8 && method !== 12)
122
146
  fail("ZIP unsupported compression method");
123
- if (version < (method === 12 ? 46 : method === 8 || flags & 8 ? 20 : 10) || version > 20 && version !== 45 && version !== 46)
147
+ // Info-ZIP uses version 10 for encrypted STORE, including descriptors.
148
+ if (version < (method === 12 ? 46 : method === 8 || flags & 8 && !(flags & 1) ? 20 : 10) || version > 20 && version !== 45 && version !== 46)
124
149
  fail("ZIP unsupported extraction version (including ZIP64)");
125
150
  }
126
151
  function dosModified(date, time) {
@@ -137,13 +162,30 @@ function dosModified(date, time) {
137
162
  fail("ZIP invalid DOS timestamp");
138
163
  return new Date(year, month, day, hours, minutes, seconds);
139
164
  }
165
+ function attributeMode(versionMadeBy, externalAttributes, directory) {
166
+ const host = versionMadeBy >>> 8;
167
+ const unixMode = host === 3 || host === 19 ? externalAttributes >>> 16 : 0;
168
+ if (unixMode)
169
+ return unixMode;
170
+ const mode = directory ? 0o040755 : 0o100644;
171
+ return externalAttributes & 1 ? mode & ~0o222 : mode;
172
+ }
140
173
  function entryBounds(entry, limits) {
141
174
  pathBytes(entry.name, limits);
142
- number(entry.size, Math.min(limits.maxEntryBytes, limits.maxTotalBytes, 0xfffffffe), "entry byte");
143
- number(entry.data.length, Math.min(limits.maxArchiveBytes, 0xfffffffe), "compressed byte");
175
+ number(entry.size, Math.min(limits.maxEntryBytes, limits.maxTotalBytes), "entry byte");
176
+ number(entry.data.length, limits.maxArchiveBytes, "compressed byte");
144
177
  number(entry.crc32, 0xffffffff, "CRC32");
145
178
  number(entry.mode, 0xffff, "mode");
146
- format(entry.method, entry.flags ?? 0x800, entry.method === 12 ? 46 : entry.method === 8 || (entry.flags ?? 0) & 8 ? 20 : 10);
179
+ format(entry.method, entry.flags ?? 0x800, entry.method === 14 ? 63 : entry.method === 99 ? 51 : entry.method === 12 ? 46 : entry.method === 8 || (entry.flags ?? 0) & 9 ? 20 : 10);
180
+ if (entry.method === 99 && !entry.aes || entry.method !== 99 && entry.aes)
181
+ fail("ZIP inconsistent AES metadata");
182
+ if (entry.aes) {
183
+ aesParameters(entry.aes);
184
+ if (entry.aes.method !== 0 && entry.aes.method !== 8 && entry.aes.method !== 12)
185
+ fail("ZIP unsupported AES compression method");
186
+ if (entry.aes.version === 2 && entry.crc32 !== 0)
187
+ fail("ZIP AE-2 CRC must be zero");
188
+ }
147
189
  if (entry.directory !== entry.name.endsWith("/") || entry.directory && (entry.symlink || entry.size !== 0))
148
190
  fail("ZIP inconsistent directory metadata");
149
191
  const type = entry.mode & 0o170000;
@@ -161,11 +203,12 @@ async function copyBytes(source, destination, start, chunkSize, signal) {
161
203
  await yieldTurn(signal);
162
204
  }
163
205
  }
164
- export async function readZipArchive(bytes, limits, signal) {
206
+ export async function readZipArchive(bytes, limits, signal, profile = {}) {
165
207
  const chunkSize = admit(limits, signal);
166
- number(bytes.length, Math.min(limits.maxArchiveBytes, 0xfffffffe), "archive byte");
208
+ number(bytes.length, limits.maxArchiveBytes, "archive byte");
167
209
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
168
210
  let end = -1;
211
+ let displacement = 0;
169
212
  const lower = Math.max(0, bytes.length - 22 - 65535);
170
213
  for (let offset = bytes.length - 22; offset >= lower; offset--) {
171
214
  if ((bytes.length - offset) % 4096 === 0)
@@ -174,23 +217,58 @@ export async function readZipArchive(bytes, limits, signal) {
174
217
  const size = view.getUint32(offset + 12, true);
175
218
  const start = view.getUint32(offset + 16, true);
176
219
  const zip64 = size === 0xffffffff || start === 0xffffffff || offset >= 20 && view.getUint32(offset - 20, true) === 0x07064b50;
177
- if (!zip64 && start + size !== offset)
178
- continue;
220
+ let candidateDisplacement = 0;
221
+ if (!zip64 && !profile.disks && start + size !== offset) {
222
+ if (!profile.prefix || start + size > offset || offset - size < 0 || size && view.getUint32(offset - size, true) !== 0x02014b50)
223
+ continue;
224
+ candidateDisplacement = offset - size - start;
225
+ }
226
+ if (zip64 && profile.prefix && !profile.disks && offset >= 76 && view.getUint32(offset - 20, true) === 0x07064b50) {
227
+ const declared = view.getBigUint64(offset - 12, true);
228
+ if (declared > BigInt(Number.MAX_SAFE_INTEGER))
229
+ fail("ZIP64 unsafe numeric field");
230
+ if (Number(declared) + 12 > offset - 20 || view.getUint32(Number(declared), true) !== 0x06064b50) {
231
+ let found = -1;
232
+ const low = Math.max(0, offset - 76 - limits.maxPaxBytes);
233
+ for (let wide = offset - 76; wide >= low; wide--) {
234
+ if (offset - 76 - wide > limits.maxPatternSteps)
235
+ fail("ZIP SFX scan work limit exceeded");
236
+ if ((offset - wide) % 4096 === 0)
237
+ await yieldTurn(signal);
238
+ if (view.getUint32(wide, true) !== 0x06064b50 || view.getBigUint64(wide + 4, true) !== BigInt(offset - 32 - wide))
239
+ continue;
240
+ if (found !== -1)
241
+ fail("ZIP64 ambiguous SFX end records");
242
+ found = wide;
243
+ }
244
+ if (found < Number(declared))
245
+ continue;
246
+ candidateDisplacement = found - Number(declared);
247
+ }
248
+ }
179
249
  if (end !== -1)
180
250
  fail("ZIP ambiguous end records");
181
251
  end = offset;
252
+ displacement = candidateDisplacement;
182
253
  }
183
254
  }
184
255
  if (end === -1)
185
256
  fail("ZIP truncated or missing end of central directory");
186
- const { members, centralStart, centralEnd } = zip64Directory(view, end, limits);
257
+ const { members, centralStart, centralEnd, diskMembers, disk: finalDisk, zip64Disk, zip64DiskMembers } = zip64Directory(view, end, limits, profile.disks, displacement);
187
258
  number(bytes.length - end - 22, limits.maxTextBytes, "archive comment");
188
259
  const entries = [];
189
260
  const spans = [];
261
+ const growSpans = new Map();
190
262
  let offset = centralStart;
191
263
  let total = 0;
264
+ let finalMembers = 0;
265
+ let wideDiskMembers = 0;
192
266
  for (let index = 0; index < members; index++) {
193
267
  await yieldTurn(signal);
268
+ if (!profile.disks || offset >= profile.disks.starts[finalDisk])
269
+ finalMembers++;
270
+ if (zip64Disk >= 0 && (!profile.disks || offset >= profile.disks.starts[zip64Disk] && offset < profile.disks.starts[zip64Disk] + profile.disks.lengths[zip64Disk]))
271
+ wideDiskMembers++;
194
272
  if (offset + 46 > centralEnd || view.getUint32(offset, true) !== 0x02014b50)
195
273
  fail("ZIP truncated or invalid central header");
196
274
  const versionMadeBy = view.getUint16(offset + 4, true);
@@ -215,6 +293,7 @@ export async function readZipArchive(bytes, limits, signal) {
215
293
  const next = offset + 46 + nameLength + extraLength + commentLength;
216
294
  if (next > centralEnd)
217
295
  fail("ZIP truncated central metadata");
296
+ zipDiskRecord(profile.disks, offset, next);
218
297
  number(commentLength, limits.maxTextBytes, "entry comment");
219
298
  const rawName = bytes.subarray(offset + 46, offset + 46 + nameLength);
220
299
  const centralExtra = bytes.subarray(offset + 46 + nameLength, next - commentLength);
@@ -222,13 +301,26 @@ export async function readZipArchive(bytes, limits, signal) {
222
301
  if (flags & 0x800)
223
302
  text(comment);
224
303
  const centralMetadata = extras(centralExtra, rawName, comment, true, limits);
304
+ if (Boolean(centralMetadata.aes) !== (method === 99))
305
+ fail("ZIP inconsistent AES method and extra field");
306
+ const aes = centralMetadata.aes;
307
+ if (aes && aes.method !== 8 && flags & 6)
308
+ fail("ZIP unsupported AES compression flags");
309
+ if (aes?.version === 2 && checksum !== 0)
310
+ fail("ZIP AE-2 CRC must be zero");
311
+ if (version < 45 && (size === 0xffffffff || compressedSize === 0xffffffff || local === 0xffffffff || disk === 65535))
312
+ fail("ZIP64 unsupported member extraction version");
225
313
  const resolved = zip64Fields(centralMetadata.zip64, [size, compressedSize, local, disk === 65535 ? 0xffffffff : disk]);
226
314
  [size, compressedSize, local] = resolved;
227
- if (resolved[3])
228
- fail("ZIP multi-disk member is unsupported");
315
+ local = zipDiskOffset(profile.disks, resolved[3], local);
316
+ local += displacement;
229
317
  number(size, limits.maxEntryBytes, "entry byte");
230
318
  number(compressedSize, limits.maxArchiveBytes, "compressed entry byte");
231
- if (method === 0 && compressedSize !== size)
319
+ if (flags & 1 && compressedSize < (aes ? aesParameters(aes).saltBytes + 12 : 12))
320
+ fail("ZIP truncated encryption header");
321
+ if (aes?.method === 0 && compressedSize !== size + aesParameters(aes).saltBytes + 12)
322
+ fail("ZIP AES stored size mismatch");
323
+ if (method === 0 && compressedSize !== size + (flags & 1 ? 12 : 0))
232
324
  fail("ZIP stored size mismatch");
233
325
  total += size;
234
326
  number(total, limits.maxTotalBytes, "total byte");
@@ -240,6 +332,7 @@ export async function readZipArchive(bytes, limits, signal) {
240
332
  const localNameLength = view.getUint16(local + 26, true);
241
333
  const localExtraLength = view.getUint16(local + 28, true);
242
334
  const payloadStart = local + 30 + localNameLength + localExtraLength;
335
+ zipDiskRecord(profile.disks, local, payloadStart);
243
336
  let payloadEnd = payloadStart + compressedSize;
244
337
  if (payloadEnd > centralStart)
245
338
  fail("ZIP truncated or overlapping local payload");
@@ -248,6 +341,8 @@ export async function readZipArchive(bytes, limits, signal) {
248
341
  fail("ZIP central/local filename mismatch");
249
342
  const localExtra = bytes.subarray(local + 30 + localNameLength, payloadStart);
250
343
  const localMetadata = extras(localExtra, localName, comment, false, limits);
344
+ if (aes ? !localMetadata.aes || aes.version !== localMetadata.aes.version || aes.strength !== localMetadata.aes.strength || aes.method !== localMetadata.aes.method : localMetadata.aes !== undefined)
345
+ fail("ZIP central/local AES metadata mismatch");
251
346
  const localEffective = nameFrom(localName, flags, localMetadata, limits);
252
347
  if (localMetadata.name !== undefined && localEffective !== name)
253
348
  fail("ZIP central/local Unicode name mismatch");
@@ -255,6 +350,8 @@ export async function readZipArchive(bytes, limits, signal) {
255
350
  fail("ZIP central/local timestamp mismatch");
256
351
  const rawLocalSize = view.getUint32(local + 22, true);
257
352
  const rawLocalCompressed = view.getUint32(local + 18, true);
353
+ if (version < 45 && (rawLocalSize === 0xffffffff || rawLocalCompressed === 0xffffffff))
354
+ fail("ZIP64 unsupported member extraction version");
258
355
  const [localSize, localCompressed] = zip64Fields(localMetadata.zip64, [rawLocalSize, rawLocalCompressed]);
259
356
  for (const [actual, expected] of [[view.getUint32(local + 14, true), checksum], [localCompressed, compressedSize], [localSize, size]]) {
260
357
  if (actual !== expected && (!(flags & 8) || actual !== 0))
@@ -275,29 +372,41 @@ export async function readZipArchive(bytes, limits, signal) {
275
372
  }
276
373
  if (matches.length !== 1)
277
374
  fail("ZIP truncated, ambiguous or mismatched data descriptor");
375
+ zipDiskRecord(profile.disks, payloadEnd, matches[0]);
278
376
  payloadEnd = matches[0];
279
377
  }
280
- const host = versionMadeBy >>> 8;
281
- const unixMode = host === 3 || host === 19 ? externalAttributes >>> 16 : 0;
282
378
  const directory = name.endsWith("/");
283
379
  if (Boolean(externalAttributes & 16) && !directory)
284
380
  fail("ZIP conflicting directory attributes");
285
- const mode = unixMode || (directory ? 0o040755 : 0o100644);
381
+ const mode = attributeMode(versionMadeBy, externalAttributes, directory);
286
382
  const symlink = (mode & 0o170000) === 0o120000;
287
383
  const modified = dosModified(dosDate, dosTime);
288
384
  const timestamp = centralMetadata.modified ?? localMetadata.modified;
289
385
  if (timestamp !== undefined)
290
386
  modified.setTime(timestamp);
291
387
  const entry = { name, data: bytes.subarray(payloadStart, payloadStart + compressedSize), size, method, crc32: checksum, modified, mode, directory, symlink, rawName, localName, comment, localExtra, centralExtra, flags, versionMadeBy, internalAttributes, externalAttributes, dosTime, dosDate };
388
+ if (aes)
389
+ entry.aes = aes;
292
390
  entryBounds(entry, limits);
391
+ if (profile.grow) {
392
+ if (centralMetadata.zip64 || localMetadata.zip64)
393
+ entry.zip64 = true;
394
+ growSpans.set(entry, { start: local, end: payloadEnd });
395
+ }
293
396
  entries.push(entry);
294
397
  spans.push({ start: local, end: payloadEnd });
295
398
  offset = next;
296
399
  }
297
400
  if (offset !== centralEnd)
298
401
  fail("ZIP central directory size or member count mismatch");
402
+ if (diskMembers !== 65535 && diskMembers !== finalMembers)
403
+ fail("ZIP inconsistent per-disk member count");
404
+ if (zip64DiskMembers >= 0 && zip64DiskMembers !== wideDiskMembers)
405
+ fail("ZIP64 inconsistent per-disk member count");
299
406
  spans.sort((first, second) => first.start - second.start);
300
- let covered = 0;
407
+ if (profile.disks && view.getUint32(0, true) !== 0x08074b50)
408
+ fail("ZIP missing split signature");
409
+ let covered = profile.disks ? 4 : profile.prefix ? spans[0]?.start ?? centralStart : 0;
301
410
  for (const span of spans) {
302
411
  if (span.start !== covered)
303
412
  fail("ZIP overlapping spans, gaps or self-extracting prefix are unsupported");
@@ -306,6 +415,12 @@ export async function readZipArchive(bytes, limits, signal) {
306
415
  if (covered !== centralStart)
307
416
  fail("ZIP unreferenced local data is unsupported");
308
417
  for (const entry of entries) {
418
+ const span = growSpans.get(entry);
419
+ if (span) {
420
+ const record = new Uint8Array(span.end - span.start);
421
+ await copyBytes(bytes.subarray(span.start, span.end), record, 0, chunkSize, signal);
422
+ zipGrowRecords.set(entry, record);
423
+ }
309
424
  const data = new Uint8Array(entry.data.length);
310
425
  await copyBytes(entry.data, data, 0, chunkSize, signal);
311
426
  entry.data = data;
@@ -316,17 +431,50 @@ export async function readZipArchive(bytes, limits, signal) {
316
431
  entry.centralExtra = new Uint8Array(entry.centralExtra);
317
432
  await yieldTurn(signal);
318
433
  }
319
- return { entries, comment: new Uint8Array(bytes.subarray(end + 22)) };
434
+ return { entries, comment: new Uint8Array(bytes.subarray(end + 22)), ...(profile.prefix ? { prefix: new Uint8Array(bytes.subarray(0, spans[0]?.start ?? centralStart)) } : {}) };
435
+ }
436
+ export async function* decodeZipEntry(entry, limits, signal, password) {
437
+ if (!entry.aes) {
438
+ yield* decodeZipContent(entry, limits, signal, password);
439
+ return;
440
+ }
441
+ admit(limits, signal);
442
+ entryBounds(entry, limits);
443
+ if (password === undefined)
444
+ fail("ZIP password required");
445
+ // Budget ciphertext's authentication copy, compressed plaintext and verified
446
+ // decompressed output together. The archive input is separately bounded.
447
+ const available = limits.maxBufferedFileBytes - entry.data.length * 2;
448
+ if (available < entry.size * 2)
449
+ fail("ZIP AES authenticated staging limit exceeded");
450
+ const plaintext = await decryptAesPayload(entry.data, password, entry.aes, available, signal);
451
+ let verified;
452
+ try {
453
+ verified = await collectBytes(decodeZipContent(entry, limits, signal, password, plaintext), { maxBytes: Math.floor((available - plaintext.length) / 2), signal });
454
+ yield* wireChunks(verified, Math.min(limits.chunkSize, 65536), signal);
455
+ }
456
+ finally {
457
+ plaintext.fill(0);
458
+ verified?.fill(0);
459
+ }
320
460
  }
321
- export async function* decodeZipEntry(entry, limits, signal) {
461
+ async function* decodeZipContent(entry, limits, signal, password, authenticated) {
322
462
  const chunkSize = admit(limits, signal);
323
463
  entryBounds(entry, limits);
324
- const reader = new CodecReader((async function* () { yield entry.data; })(), signal);
464
+ if (entry.flags & 1 && password === undefined)
465
+ fail("ZIP password required");
466
+ const input = (async function* () { yield authenticated ?? entry.data; })();
467
+ const decrypted = authenticated === undefined && entry.flags & 1 ? decryptZipPayload(input, password, entry.flags & 8 ? (entry.dosTime ?? 0) >>> 8 : entry.crc32 >>> 24, signal) : input;
468
+ const reader = new CodecReader(decrypted, signal);
325
469
  let length = 0;
326
470
  let checksum = 0;
327
- const source = entry.method === 12 ? boundedCodec(reader, { format: "bzip2", decompress: true, level: 9, singleMember: true }, signal) : entry.method === 8 ? codec(reader, { mode: "inflate-raw", chunkSize }, signal) : (async function* () {
328
- for (let offset = 0; offset < entry.data.length; offset += chunkSize) {
329
- yield new Uint8Array(entry.data.subarray(offset, offset + chunkSize));
471
+ const method = entry.aes?.method ?? entry.method;
472
+ const source = method === 14 ? zipLzma(reader, signal, { decode: true, level: 1, eos: Boolean(entry.flags & 2), size: entry.size }) : method === 12 ? boundedCodec(reader, { format: "bzip2", decompress: true, level: 9, singleMember: true }, signal) : method === 8 ? codec(reader, { mode: "inflate-raw", chunkSize }, signal) : (async function* () {
473
+ for (;;) {
474
+ const chunk = await reader.chunk();
475
+ if (chunk === undefined)
476
+ break;
477
+ yield new Uint8Array(chunk);
330
478
  await yieldTurn(signal);
331
479
  }
332
480
  })();
@@ -334,15 +482,15 @@ export async function* decodeZipEntry(entry, limits, signal) {
334
482
  for await (const chunk of source) {
335
483
  signal.throwIfAborted();
336
484
  length += chunk.length;
337
- number(length, Math.min(limits.maxEntryBytes, limits.maxTotalBytes), "decoded byte");
485
+ number(length, Math.min(limits.maxEntryBytes, limits.maxTotalBytes, method === 14 ? entry.size : Number.MAX_SAFE_INTEGER), "decoded byte");
338
486
  checksum = crc32(chunk, checksum);
339
487
  yield chunk;
340
488
  }
341
- if (entry.method !== 0 && await reader.chunk() !== undefined)
489
+ if (method !== 0 && await reader.chunk() !== undefined)
342
490
  fail("ZIP trailing compressed data");
343
491
  if (length !== entry.size)
344
492
  fail("ZIP uncompressed size mismatch");
345
- if (checksum !== entry.crc32)
493
+ if (entry.aes?.version !== 2 && checksum !== entry.crc32)
346
494
  fail("ZIP CRC32 mismatch");
347
495
  }
348
496
  finally {
@@ -375,7 +523,7 @@ export async function makeZipEntry(name, bytes, attributes, limits, signal, leve
375
523
  if (level !== 0 && (bytes.length || forceCompression) && !entry.directory && !entry.symlink) {
376
524
  const chunks = [];
377
525
  let length = 0;
378
- const source = method === "bzip2" ? boundedCodec(reader, { format: "bzip2", decompress: false, level }, signal) : codec(reader, { mode: "deflate-raw", chunkSize, level }, signal);
526
+ const source = method === "lzma" ? zipLzma(reader, signal, { decode: false, level, eos: true, size: bytes.length }) : method === "bzip2" ? boundedCodec(reader, { format: "bzip2", decompress: false, level }, signal) : codec(reader, { mode: "deflate-raw", chunkSize, level }, signal);
379
527
  for await (const chunk of source) {
380
528
  length += chunk.length;
381
529
  if (length > limits.maxArchiveBytes)
@@ -392,7 +540,9 @@ export async function makeZipEntry(name, bytes, attributes, limits, signal, leve
392
540
  offset += chunk.length;
393
541
  await yieldTurn(signal);
394
542
  }
395
- entry.method = method === "bzip2" ? 12 : 8;
543
+ entry.method = method === "lzma" ? 14 : method === "bzip2" ? 12 : 8;
544
+ if (entry.method === 14)
545
+ entry.flags = 0x802;
396
546
  return entry;
397
547
  }
398
548
  }
@@ -429,7 +579,7 @@ export function updateZipExtras(entry, previous, mode, limits) {
429
579
  const identifier = view.getUint16(offset, true);
430
580
  const next = offset + 4 + view.getUint16(offset + 2, true);
431
581
  // Sizes, timestamps and Unicode names/comments are rebuilt for the new entry.
432
- if (identifier !== 1 && identifier !== 0x5455 && identifier !== 0x7075 && identifier !== 0x6375) {
582
+ if (identifier !== 1 && identifier !== 0x9901 && identifier !== 0x5455 && identifier !== 0x7075 && identifier !== 0x6375) {
433
583
  fields.push(original.subarray(offset, next));
434
584
  length += next - offset;
435
585
  }
@@ -492,38 +642,166 @@ export function setZipEntryComment(entry, comment, limits) {
492
642
  }
493
643
  return { ...entry, comment: new Uint8Array(comment), flags, localExtra: metadata[0], centralExtra: metadata[1] };
494
644
  }
495
- export async function* streamZipArchive(archive, limits, signal, descriptors = false, forceZip64 = false) {
645
+ export async function* streamZipArchive(archive, limits, signal, descriptors = false, forceZip64 = false, allowZip64 = true) {
496
646
  const chunkSize = admit(limits, signal);
647
+ if (archive.entries.some(entry => entry.encryption?.aes && !entry.directory)) {
648
+ number(archive.entries.length, limits.maxMembers, "member");
649
+ if (archive.entries.length > limits.maxPatternSteps)
650
+ fail("ZIP archive work limit exceeded");
651
+ const entries = [];
652
+ let total = 0;
653
+ let stagedBytes = 0;
654
+ let retained = archive.entries.reduce((sum, entry) => sum + entry.data.length, 0);
655
+ number(retained, limits.maxArchiveBytes, "compressed byte");
656
+ for (const original of archive.entries) {
657
+ await yieldTurn(signal);
658
+ total += original.source ? 0 : original.size;
659
+ number(total, limits.maxTotalBytes, "total byte");
660
+ if (!original.encryption?.aes || original.directory) {
661
+ entries.push(original);
662
+ continue;
663
+ }
664
+ entryBounds(original, limits);
665
+ const aes = { ...original.encryption.aes };
666
+ aesParameters(aes);
667
+ // Conservative allowance for overlapping owned input, codec result,
668
+ // encryption input/output and wire assembly. Codec workspace is separate.
669
+ const maximum = Math.floor((limits.maxBufferedFileBytes - 28) / 6);
670
+ if (maximum < 0)
671
+ fail("ZIP AES authenticated staging limit exceeded");
672
+ let entry = original;
673
+ if (original.source) {
674
+ const plaintext = await collectBytes(original.source, { maxBytes: Math.min(maximum, limits.maxEntryBytes, limits.maxTotalBytes - total), signal });
675
+ total += plaintext.length;
676
+ try {
677
+ if (original.expectedSize !== undefined && plaintext.length !== original.expectedSize)
678
+ fail("ZIP live input size mismatch");
679
+ const made = await makeZipEntry(original.name, plaintext, { modified: original.modified, mode: original.mode, directory: original.directory, symlink: original.symlink }, limits, signal, original.method === 0 ? 0 : original.level ?? 6, original.method !== 0, original.method === 14 ? "lzma" : original.method === 12 ? "bzip2" : "deflate");
680
+ entry = { ...original, ...made };
681
+ delete entry.source;
682
+ delete entry.expectedSize;
683
+ }
684
+ finally {
685
+ plaintext.fill(0);
686
+ }
687
+ }
688
+ if (entry.data.length > maximum || entry.size > maximum)
689
+ fail("ZIP AES authenticated staging limit exceeded");
690
+ const overhead = aesParameters(aes).saltBytes + 12;
691
+ retained += entry.data.length + overhead - original.data.length;
692
+ number(retained, limits.maxArchiveBytes, "encrypted byte");
693
+ const data = await encryptAesPayload((async function* () { yield entry.data; })(), { ...original.encryption, aes }, maximum, signal);
694
+ stagedBytes += data.length;
695
+ if (original.source) {
696
+ original.size = entry.size;
697
+ original.compressedSize = data.length;
698
+ original.crc32 = entry.crc32;
699
+ if (entry.internalAttributes !== undefined)
700
+ original.internalAttributes = entry.internalAttributes;
701
+ }
702
+ const extra = aesExtra(aes, entry.method);
703
+ const append = (bytes) => {
704
+ const joined = new Uint8Array(bytes.length + extra.length);
705
+ joined.set(bytes);
706
+ joined.set(extra, bytes.length);
707
+ return joined;
708
+ };
709
+ const encrypted = { ...entry, data, aes: { ...aes, method: entry.method }, method: 99, crc32: aes.version === 2 ? 0 : entry.crc32,
710
+ flags: (((entry.flags ?? 0x800) | 1) & ~6) | (descriptors || original.descriptors ? 8 : 0),
711
+ localExtra: append(entry.localExtra ?? timestampExtra(entry.modified)), centralExtra: append(entry.centralExtra ?? timestampExtra(entry.modified)) };
712
+ delete encrypted.encryption;
713
+ entries.push(encrypted);
714
+ }
715
+ yield* streamZipArchive({ ...archive, entries, ...(archive.onRetention ? { onRetention: (counters) => archive.onRetention({ ...counters, payloadBytes: counters.payloadBytes + stagedBytes }) } : {}) }, limits, signal, descriptors, forceZip64, allowZip64);
716
+ return;
717
+ }
718
+ if (archive.entries.some(entry => entry.encryption && !entry.source && !entry.directory)) {
719
+ const entries = [];
720
+ for (const original of archive.entries) {
721
+ if (!original.encryption || original.source || original.directory) {
722
+ entries.push(original);
723
+ continue;
724
+ }
725
+ entryBounds(original, limits);
726
+ number(original.data.length + 12, limits.maxArchiveBytes, "encrypted byte");
727
+ const rounded = new Date(Math.ceil(Math.floor(original.modified.getTime() / 1000) / 2) * 2000);
728
+ const time = original.dosTime ?? ((rounded.getHours() << 11) | (rounded.getMinutes() << 5) | (rounded.getSeconds() >>> 1));
729
+ const descriptor = descriptors || original.descriptors === true;
730
+ const data = await collectBytes(encryptZipPayload((async function* () { yield original.data; })(), original.encryption, descriptor ? time >>> 8 : original.crc32 >>> 24, signal), { maxBytes: limits.maxArchiveBytes, signal });
731
+ const entry = { ...original, data, dosTime: time, flags: (original.flags ?? 0x800) | 1 | (descriptor ? 8 : 0) };
732
+ delete entry.encryption;
733
+ entries.push(entry);
734
+ }
735
+ yield* streamZipArchive({ ...archive, entries }, limits, signal, descriptors, forceZip64, allowZip64);
736
+ return;
737
+ }
497
738
  number(archive.entries.length, limits.maxMembers, "member");
498
739
  number(archive.comment.length, Math.min(limits.maxTextBytes, 65535), "archive comment");
499
- const wideArchive = forceZip64 || archive.entries.length > 65535 || archive.entries.some(entry => entry.zip64);
740
+ const wideProfile = (entry) => {
741
+ const required = Math.max(entry.size, entry.data.length, entry.expectedSize ?? 0) >= 0xffffffff;
742
+ const unknown = entry.source !== undefined && entry.zip64 !== false && allowZip64 &&
743
+ (entry.expectedSize === undefined && limits.maxEntryBytes >= 0xffffffff || entry.method !== 0 && limits.maxArchiveBytes >= 0xffffffff);
744
+ return forceZip64 || entry.zip64 === true || required || unknown;
745
+ };
746
+ let wideArchive = archive.entries.length >= 65535 || forceZip64 || archive.entries.some(wideProfile);
747
+ if (wideArchive && !allowZip64)
748
+ fail("ZIP64 required but disabled");
500
749
  let length = 22 + archive.comment.length + (wideArchive ? 76 : 0);
750
+ number(length, limits.maxArchiveBytes, "archive byte");
501
751
  let localLength = 0;
502
752
  let total = 0;
753
+ let metadataBytes = 0;
754
+ let work = 0;
503
755
  const encoded = [];
504
756
  for (const entry of archive.entries) {
757
+ if (++work > limits.maxPatternSteps)
758
+ fail("ZIP archive work limit exceeded");
505
759
  await yieldTurn(signal);
506
760
  entryBounds(entry, limits);
507
- if (entry.method === 0 && entry.data.length !== entry.size)
761
+ if (!entry.source && entry.method === 0 && entry.data.length !== entry.size + (entry.flags & 1 ? 12 : 0))
508
762
  fail("ZIP stored size mismatch");
509
- total += entry.size;
763
+ if (entry.source && (entry.data.length || entry.directory || entry.symlink))
764
+ fail("ZIP invalid live source profile");
765
+ if (entry.expectedSize !== undefined)
766
+ number(entry.expectedSize, Math.min(limits.maxEntryBytes, limits.maxTotalBytes), "expected input byte");
767
+ if (entry.source)
768
+ number(entry.level ?? 6, 9, "compression level");
769
+ total += entry.source ? 0 : entry.size;
510
770
  number(total, limits.maxTotalBytes, "total byte");
511
771
  const rawName = entry.rawName ?? pathBytes(entry.name, limits);
512
- const descriptor = (descriptors || entry.descriptors === true) && !entry.directory;
513
- const flags = ((entry.flags ?? 0x800) & ~8) | (descriptor ? 8 : 0);
772
+ // Retained ciphertext binds its verifier to the original descriptor flag.
773
+ const descriptor = entry.flags & 1 || zipGrowRecords.has(entry) ? Boolean(entry.flags & 8) : (entry.source !== undefined || descriptors || entry.descriptors === true) && !entry.directory;
774
+ const flags = ((entry.flags ?? (entry.method === 14 ? 0x802 : 0x800)) & ~8) | (descriptor ? 8 : 0) | (entry.encryption && !entry.directory ? 1 : 0) | (entry.source && entry.method === 14 ? 2 : 0);
514
775
  const comment = entry.comment ?? new Uint8Array();
515
776
  number(comment.length, Math.min(limits.maxTextBytes, 65535), "entry comment");
516
777
  if (flags & 0x800)
517
778
  text(comment);
518
779
  const originalLocalExtra = entry.localExtra ?? timestampExtra(entry.modified);
519
- extras(originalLocalExtra, rawName, comment, false, limits);
520
- const wide = forceZip64 || entry.zip64 === true;
521
- const localExtra = wide ? zip64Extra([descriptor ? 0 : entry.size, descriptor ? 0 : entry.data.length], stripZip64(originalLocalExtra)) : stripZip64(originalLocalExtra);
780
+ const originalLocalMetadata = extras(originalLocalExtra, rawName, comment, false, limits);
781
+ const wide = wideProfile(entry);
782
+ if (wide && (!allowZip64 || !forceZip64 && entry.zip64 === false))
783
+ fail("ZIP64 required but disabled");
784
+ const member = zip64Member(entry.size, entry.data.length, localLength, forceZip64 || entry.zip64 === true, allowZip64 && (forceZip64 || entry.zip64 !== false), wide && descriptor);
522
785
  const originalCentralExtra = entry.centralExtra ?? timestampExtra(entry.modified);
523
- extras(originalCentralExtra, rawName, comment, true, limits);
524
- const centralExtra = wide ? zip64Extra([entry.size, entry.data.length, localLength], stripZip64(originalCentralExtra)) : stripZip64(originalCentralExtra);
786
+ const originalCentralMetadata = extras(originalCentralExtra, rawName, comment, true, limits);
787
+ const record = forceZip64 ? undefined : zipGrowRecords.get(entry);
788
+ const localValues = descriptor && wide
789
+ ? (entry.method === 12 || entry.method === 14) && !entry.source ? [entry.size, entry.data.length] : [0, 0]
790
+ : [entry.size, entry.data.length].filter(value => forceZip64 || entry.zip64 === true || value >= 0xffffffff);
791
+ const localExtraSize = record ? originalLocalExtra.length : originalLocalExtra.length - (originalLocalMetadata.zip64 ? originalLocalMetadata.zip64.length + 4 : 0) + (localValues.length ? 4 + localValues.length * 8 : 0);
792
+ const centralExtraSize = originalCentralExtra.length - (originalCentralMetadata.zip64 ? originalCentralMetadata.zip64.length + 4 : 0) + (member.values.length ? 4 + member.values.length * 8 : 0);
793
+ number(localExtraSize, Math.min(limits.maxPaxBytes, 65535), "extra field");
794
+ number(centralExtraSize, Math.min(limits.maxPaxBytes, 65535), "extra field");
795
+ const retainedSize = record
796
+ ? record.length + 46 + rawName.length + centralExtraSize + comment.length
797
+ : 76 + 2 * rawName.length + localExtraSize + centralExtraSize + comment.length + entry.data.length + (descriptor ? wide ? 24 : 16 : 0);
798
+ number(length + retainedSize, limits.maxArchiveBytes, "archive byte");
799
+ const localExtra = record ? originalLocalExtra : localValues.length ? zip64Extra(localValues, stripZip64(originalLocalExtra)) : stripZip64(originalLocalExtra);
800
+ const centralExtra = member.values.length ? zip64Extra(member.values, stripZip64(originalCentralExtra)) : stripZip64(originalCentralExtra);
525
801
  const localMetadata = extras(localExtra, rawName, comment, false, limits);
526
802
  const centralMetadata = extras(centralExtra, rawName, comment, true, limits);
803
+ if (entry.aes ? !localMetadata.aes || !centralMetadata.aes || [localMetadata.aes, centralMetadata.aes].some(aes => aes.version !== entry.aes.version || aes.strength !== entry.aes.strength || aes.method !== entry.aes.method) : localMetadata.aes || centralMetadata.aes)
804
+ fail("ZIP retained AES metadata mismatch");
527
805
  if (nameFrom(rawName, flags, centralMetadata, limits) !== entry.name || localMetadata.name !== undefined && nameFrom(rawName, flags, localMetadata, limits) !== entry.name || entry.localName && !equal(rawName, entry.localName))
528
806
  fail("ZIP retained filename metadata mismatch");
529
807
  if (localMetadata.modified !== undefined && centralMetadata.modified !== undefined && localMetadata.modified !== centralMetadata.modified)
@@ -534,71 +812,198 @@ export async function* streamZipArchive(archive, limits, signal, descriptors = f
534
812
  const time = entry.dosTime ?? ((rounded.getHours() << 11) | (rounded.getMinutes() << 5) | (rounded.getSeconds() >>> 1));
535
813
  number(date, 65535, "DOS date");
536
814
  number(time, 65535, "DOS time");
537
- const dosTimestamp = dosModified(date, time).getTime();
815
+ const dosTimestamp = dosModified(date, time);
538
816
  const extendedTimestamp = centralMetadata.modified ?? localMetadata.modified;
539
- if (extendedTimestamp === undefined ? dosTimestamp !== rounded.getTime() : Math.floor(extendedTimestamp / 1000) !== Math.floor(entry.modified.getTime() / 1000))
817
+ // DOS stores local calendar fields, so it cannot distinguish the two
818
+ // occurrences of a repeated DST hour. UT still binds the absolute instant.
819
+ const timestampMatches = extendedTimestamp === undefined
820
+ ? ["getFullYear", "getMonth", "getDate", "getHours", "getMinutes", "getSeconds"]
821
+ .every(field => dosTimestamp[field]() === rounded[field]())
822
+ : Math.floor(extendedTimestamp / 1000) === Math.floor(entry.modified.getTime() / 1000);
823
+ if (!timestampMatches)
540
824
  fail("ZIP retained or unrepresentable timestamp metadata mismatch");
541
825
  for (const [value, maximum, label] of [[entry.versionMadeBy ?? 0x31e, 65535, "creator version"], [entry.internalAttributes ?? 0, 65535, "internal attributes"], [entry.externalAttributes ?? 0, 0xffffffff, "external attributes"]])
542
826
  number(value, maximum, label);
543
827
  if ((entry.internalAttributes ?? 0) & ~1)
544
828
  fail("ZIP unsupported internal attributes");
545
829
  if (entry.externalAttributes !== undefined) {
546
- const host = (entry.versionMadeBy ?? 0x31e) >>> 8;
547
- const unixMode = host === 3 || host === 19 ? entry.externalAttributes >>> 16 : 0;
548
- const mode = unixMode || (entry.directory ? 0o040755 : 0o100644);
830
+ const mode = attributeMode(entry.versionMadeBy ?? 0x31e, entry.externalAttributes, entry.directory);
549
831
  if (mode !== entry.mode || Boolean(entry.externalAttributes & 16) && !entry.directory)
550
832
  fail("ZIP retained file attributes mismatch");
551
833
  }
552
- encoded.push({ entry, rawName, wide, flags, localExtra, centralExtra, comment, date, time, offset: localLength });
553
- localLength += 30 + rawName.length + localExtra.length + entry.data.length + (descriptor ? wide ? 24 : 16 : 0);
554
- length += 76 + 2 * rawName.length + localExtra.length + centralExtra.length + comment.length + entry.data.length + (descriptor ? wide ? 24 : 16 : 0);
555
- number(length, Math.min(limits.maxArchiveBytes, 0xfffffffe), "archive byte");
834
+ metadataBytes += 76 + 2 * rawName.length + localExtra.length + centralExtra.length + comment.length;
835
+ number(metadataBytes, limits.maxArchiveBytes, "metadata byte");
836
+ const localSize = record?.length ?? 30 + rawName.length + localExtra.length + entry.data.length + (descriptor ? wide ? 24 : 16 : 0);
837
+ length += localSize + 46 + rawName.length + centralExtra.length + comment.length;
838
+ number(length, limits.maxArchiveBytes, "archive byte");
839
+ const metadata = { ...entry, data: new Uint8Array() };
840
+ delete metadata.source;
841
+ delete metadata.encryption;
842
+ const item = { entry: metadata, rawName, wide, flags, localExtra, centralExtra, comment, date, time, offset: localLength, compressedSize: entry.data.length };
843
+ encoded.push(item);
844
+ archive.onRetention?.({ metadataBytes, payloadBytes: 0 });
845
+ localLength += localSize;
846
+ }
847
+ const plannedCentralSize = length - (22 + archive.comment.length + (wideArchive ? 76 : 0)) - localLength;
848
+ if (!wideArchive && (localLength >= 0xffffffff || plannedCentralSize >= 0xffffffff)) {
849
+ if (!allowZip64)
850
+ fail("ZIP64 required but disabled");
851
+ number(length + 76, limits.maxArchiveBytes, "archive byte");
852
+ length += 76;
853
+ wideArchive = true;
556
854
  }
557
- number(length, Math.min(limits.maxArchiveBytes, 0xfffffffe), "archive byte");
558
- for (const item of encoded) {
855
+ const refreshCentral = (item, size, compressed, offset) => {
856
+ const member = zip64Member(size, compressed, offset, forceZip64 || item.entry.zip64 === true, allowZip64 && (forceZip64 || item.entry.zip64 !== false), item.wide && Boolean(item.flags & 8));
857
+ const retained = stripZip64(item.centralExtra);
858
+ const extraSize = retained.length + (member.values.length ? 4 + member.values.length * 8 : 0);
859
+ number(extraSize, Math.min(limits.maxPaxBytes, 65535), "extra field");
860
+ const delta = extraSize - item.centralExtra.length;
861
+ const overhead = !wideArchive && member.wide ? 76 : 0;
862
+ number(length + delta + overhead, limits.maxArchiveBytes, "archive byte");
863
+ length += delta + overhead;
864
+ metadataBytes += delta;
865
+ wideArchive ||= member.wide;
866
+ item.centralExtra = member.values.length ? zip64Extra(member.values, retained) : retained;
867
+ };
868
+ localLength = 0;
869
+ for (const [index, item] of encoded.entries()) {
870
+ const entry = archive.entries[index];
871
+ const { rawName, wide, flags, localExtra, date, time } = item;
872
+ const descriptor = Boolean(flags & 8);
873
+ item.offset = localLength;
874
+ refreshCentral(item, entry.size, entry.data.length, localLength);
559
875
  await yieldTurn(signal);
560
- const { entry, rawName, wide, flags, localExtra, date, time } = item;
876
+ const record = forceZip64 ? undefined : zipGrowRecords.get(entry);
877
+ if (record) {
878
+ yield* wireChunks(record, chunkSize, signal);
879
+ localLength += record.length;
880
+ continue;
881
+ }
561
882
  const offset = 0;
562
883
  const bytes = new Uint8Array(30 + rawName.length + localExtra.length);
563
884
  const view = new DataView(bytes.buffer);
564
- const version = entry.method === 12 ? 46 : wide ? 45 : entry.method === 8 || flags & 8 ? 20 : 10;
885
+ const version = entry.method === 14 ? 63 : entry.method === 99 ? 51 : entry.method === 12 ? 46 : wide || item.offset >= 0xffffffff ? 45 : entry.method === 8 || flags & 9 ? 20 : 10;
565
886
  view.setUint32(offset, 0x04034b50, true);
566
887
  view.setUint16(offset + 4, version, true);
567
888
  view.setUint16(offset + 6, flags, true);
568
889
  view.setUint16(offset + 8, entry.method, true);
569
890
  view.setUint16(offset + 10, time, true);
570
891
  view.setUint16(offset + 12, date, true);
571
- view.setUint32(offset + 14, flags & 8 ? 0 : entry.crc32, true);
572
- view.setUint32(offset + 18, wide ? 0xffffffff : flags & 8 ? 0 : entry.data.length, true);
573
- view.setUint32(offset + 22, wide ? 0xffffffff : flags & 8 ? 0 : entry.size, true);
892
+ // Buffered BZIP2/LZMA have known compressed spans. Retain them with a
893
+ // descriptor so streaming native readers can locate the member boundary.
894
+ const knownCompressedSpan = (entry.method === 12 || entry.method === 14) && !entry.source;
895
+ view.setUint32(offset + 14, flags & 8 && !knownCompressedSpan ? 0 : entry.crc32, true);
896
+ view.setUint32(offset + 18, wide && (descriptor || forceZip64 || entry.zip64 === true || entry.data.length >= 0xffffffff) ? 0xffffffff : flags & 8 && !knownCompressedSpan ? 0 : entry.data.length, true);
897
+ view.setUint32(offset + 22, wide && (descriptor || forceZip64 || entry.zip64 === true || entry.size >= 0xffffffff) ? 0xffffffff : flags & 8 && !knownCompressedSpan ? 0 : entry.size, true);
574
898
  view.setUint16(offset + 26, rawName.length, true);
575
899
  view.setUint16(offset + 28, localExtra.length, true);
576
900
  bytes.set(rawName, offset + 30);
577
901
  bytes.set(localExtra, offset + 30 + rawName.length);
578
902
  yield* wireChunks(bytes, chunkSize, signal);
579
- yield* wireChunks(entry.data, chunkSize, signal);
580
- if (flags & 8) {
581
- const descriptor = 0;
582
- const bytes = new Uint8Array(wide ? 24 : 16);
583
- const view = new DataView(bytes.buffer);
584
- view.setUint32(descriptor, 0x08074b50, true);
585
- view.setUint32(descriptor + 4, entry.crc32, true);
586
- if (wide) {
587
- view.setBigUint64(descriptor + 8, BigInt(entry.data.length), true);
588
- view.setBigUint64(descriptor + 16, BigInt(entry.size), true);
903
+ if (entry.source) {
904
+ let size = 0;
905
+ let checksum = 0;
906
+ let textual = false;
907
+ let binary = false;
908
+ let previousSlab = 0;
909
+ const measured = (async function* () {
910
+ for await (const chunk of readBytes(entry.source, signal)) {
911
+ if (chunk.length === 0) {
912
+ if (++work > limits.maxPatternSteps)
913
+ fail("ZIP archive work limit exceeded");
914
+ await yieldTurn(signal);
915
+ continue;
916
+ }
917
+ number(size + chunk.length, Math.min(limits.maxEntryBytes, limits.maxTotalBytes - total, entry.expectedSize ?? Number.MAX_SAFE_INTEGER), "input byte");
918
+ if (!wide)
919
+ number(size + chunk.length, 0xfffffffe, "classic input byte");
920
+ // STORE output has the same size as its input. Admit it before
921
+ // copying any producer bytes into an owned slab.
922
+ if (entry.method === 0) {
923
+ const overhead = !wideArchive && localLength + bytes.length + item.compressedSize + chunk.length + (descriptor ? wide ? 24 : 16 : 0) >= 0xffffffff ? 76 : 0;
924
+ number(length + chunk.length + overhead, limits.maxArchiveBytes, "archive byte");
925
+ }
926
+ for (let offset = 0; offset < chunk.length; offset += chunkSize) {
927
+ if (++work > limits.maxPatternSteps)
928
+ fail("ZIP archive work limit exceeded");
929
+ const view = chunk.subarray(offset, Math.min(chunk.length, offset + chunkSize));
930
+ size += view.length;
931
+ checksum = crc32(view, checksum);
932
+ if (entry.method !== 0)
933
+ for (const byte of view) {
934
+ if (byte <= 6 || byte >= 14 && byte <= 25 || byte >= 28 && byte <= 31)
935
+ binary = true;
936
+ else if (byte === 9 || byte === 10 || byte === 13 || byte >= 32)
937
+ textual = true;
938
+ }
939
+ // A consumer can still reference the consumed slab while next()
940
+ // admits its replacement. Account for both before allocating.
941
+ archive.onRetention?.({ metadataBytes, payloadBytes: previousSlab + view.length });
942
+ previousSlab = view.length;
943
+ yield new Uint8Array(view);
944
+ await yieldTurn(signal);
945
+ }
946
+ }
947
+ if (entry.expectedSize !== undefined && size !== entry.expectedSize)
948
+ fail("ZIP source changed while reading");
949
+ })();
950
+ const reader = new CodecReader(measured, signal);
951
+ const payload = entry.method === 14 ? zipLzma(reader, signal, { decode: false, level: entry.level ?? 6, eos: true, size: entry.expectedSize ?? 0 }) : entry.method === 12 ? boundedCodec(reader, { format: "bzip2", decompress: false, level: entry.level ?? 6 }, signal)
952
+ : entry.method === 8 ? codec(reader, { mode: "deflate-raw", level: entry.level ?? 6, chunkSize }, signal) : measured;
953
+ item.compressedSize = 0;
954
+ try {
955
+ const wire = entry.encryption ? encryptZipPayload(payload, entry.encryption, time >>> 8, signal) : payload;
956
+ for await (const chunk of wire) {
957
+ if (++work > limits.maxPatternSteps)
958
+ fail("ZIP archive work limit exceeded");
959
+ if (!wideArchive && localLength + bytes.length + item.compressedSize + chunk.length + (descriptor ? wide ? 24 : 16 : 0) >= 0xffffffff) {
960
+ if (!allowZip64)
961
+ fail("ZIP64 required but disabled");
962
+ number(length + chunk.length + 76, limits.maxArchiveBytes, "archive byte");
963
+ length += 76;
964
+ wideArchive = true;
965
+ }
966
+ number(length + chunk.length, limits.maxArchiveBytes, "archive byte");
967
+ if (!wide)
968
+ number(item.compressedSize + chunk.length, 0xfffffffe, "classic compressed byte");
969
+ length += chunk.length;
970
+ item.compressedSize += chunk.length;
971
+ yield chunk;
972
+ signal.throwIfAborted();
973
+ }
589
974
  }
590
- else {
591
- view.setUint32(descriptor + 8, entry.data.length, true);
592
- view.setUint32(descriptor + 12, entry.size, true);
975
+ finally {
976
+ await reader.close();
593
977
  }
978
+ total += size;
979
+ item.entry.size = size;
980
+ item.entry.crc32 = checksum;
981
+ if (entry.method !== 0)
982
+ item.entry.internalAttributes = !binary && textual ? 1 : 0;
983
+ refreshCentral(item, size, item.compressedSize, item.offset);
984
+ archive.onRetention?.({ metadataBytes, payloadBytes: 0 });
985
+ entry.size = size;
986
+ entry.crc32 = checksum;
987
+ entry.compressedSize = item.compressedSize;
988
+ if (item.entry.internalAttributes !== undefined)
989
+ entry.internalAttributes = item.entry.internalAttributes;
990
+ }
991
+ else
992
+ yield* wireChunks(entry.data, chunkSize, signal);
993
+ localLength += bytes.length + item.compressedSize + (descriptor ? wide ? 24 : 16 : 0);
994
+ if (flags & 8) {
995
+ const bytes = zipDescriptor(item.entry.crc32, item.compressedSize, item.entry.size, wide);
594
996
  yield* wireChunks(bytes, chunkSize, signal);
595
997
  }
596
998
  }
597
999
  let centralSize = 0;
598
- for (const item of encoded) {
1000
+ for (const [index, item] of encoded.entries()) {
599
1001
  await yieldTurn(signal);
600
1002
  const { entry, rawName, wide, flags, centralExtra, comment, date, time, offset } = item;
601
- const version = entry.method === 12 ? 46 : wide ? 45 : entry.method === 8 || flags & 8 ? 20 : 10;
1003
+ const member = zip64Member(entry.size, item.compressedSize, offset, forceZip64 || entry.zip64 === true, allowZip64 && (forceZip64 || entry.zip64 !== false), wide && Boolean(flags & 8));
1004
+ const record = forceZip64 ? undefined : zipGrowRecords.get(archive.entries[index]);
1005
+ const version = record ? new DataView(record.buffer, record.byteOffset, record.byteLength).getUint16(4, true)
1006
+ : entry.method === 14 ? 63 : entry.method === 99 ? 51 : entry.method === 12 ? 46 : wide || member.wide ? 45 : entry.method === 8 || flags & 9 ? 20 : 10;
602
1007
  const central = 0;
603
1008
  const bytes = new Uint8Array(46 + rawName.length + centralExtra.length + comment.length);
604
1009
  const view = new DataView(bytes.buffer);
@@ -610,40 +1015,33 @@ export async function* streamZipArchive(archive, limits, signal, descriptors = f
610
1015
  view.setUint16(central + 12, time, true);
611
1016
  view.setUint16(central + 14, date, true);
612
1017
  view.setUint32(central + 16, entry.crc32, true);
613
- view.setUint32(central + 20, wide ? 0xffffffff : entry.data.length, true);
614
- view.setUint32(central + 24, wide ? 0xffffffff : entry.size, true);
1018
+ view.setUint32(central + 20, member.compressed, true);
1019
+ view.setUint32(central + 24, member.size, true);
615
1020
  view.setUint16(central + 28, rawName.length, true);
616
1021
  view.setUint16(central + 30, centralExtra.length, true);
617
1022
  view.setUint16(central + 32, comment.length, true);
618
1023
  view.setUint16(central + 36, entry.internalAttributes ?? 0, true);
619
1024
  const mode = entry.mode & 0o170000 ? entry.mode : entry.mode | (entry.directory ? 0o040000 : entry.symlink ? 0o120000 : 0o100000);
620
1025
  view.setUint32(central + 38, entry.externalAttributes ?? (mode * 65536 + (entry.directory ? 16 : 0)), true);
621
- view.setUint32(central + 42, wide ? 0xffffffff : offset, true);
1026
+ view.setUint32(central + 42, member.offset, true);
622
1027
  bytes.set(rawName, central + 46);
623
1028
  bytes.set(centralExtra, central + 46 + rawName.length);
624
1029
  bytes.set(comment, central + 46 + rawName.length + centralExtra.length);
625
1030
  centralSize += bytes.length;
626
1031
  yield* wireChunks(bytes, chunkSize, signal);
627
1032
  }
628
- const bytes = new Uint8Array((wideArchive ? 76 : 0) + 22 + archive.comment.length);
629
- const view = new DataView(bytes.buffer);
630
- const central = wideArchive ? writeZip64End(view, 0, encoded.length, centralSize, localLength, localLength + centralSize) : 0;
631
- view.setUint32(central, 0x06054b50, true);
632
- view.setUint16(central + 8, Math.min(encoded.length, 65535), true);
633
- view.setUint16(central + 10, Math.min(encoded.length, 65535), true);
634
- view.setUint32(central + 12, centralSize, true);
635
- view.setUint32(central + 16, wideArchive ? 0xffffffff : localLength, true);
636
- view.setUint16(central + 20, archive.comment.length, true);
637
- bytes.set(archive.comment, central + 22);
1033
+ number(localLength + centralSize + 22 + archive.comment.length + (wideArchive || centralSize >= 0xffffffff || localLength >= 0xffffffff ? 76 : 0), limits.maxArchiveBytes, "archive byte");
1034
+ const bytes = zipEnd(encoded.length, centralSize, localLength, archive.comment, wideArchive, allowZip64);
638
1035
  yield* wireChunks(bytes, chunkSize, signal);
639
1036
  }
640
1037
  async function* wireChunks(bytes, chunkSize, signal) {
641
1038
  for (let offset = 0; offset < bytes.length; offset += chunkSize) {
642
1039
  await yieldTurn(signal);
643
1040
  yield new Uint8Array(bytes.subarray(offset, Math.min(bytes.length, offset + chunkSize)));
1041
+ signal.throwIfAborted();
644
1042
  }
645
1043
  }
646
- export async function writeZipArchive(archive, limits, signal, descriptors = false, forceZip64 = false) {
647
- return collectBytes(streamZipArchive(archive, limits, signal, descriptors, forceZip64), { maxBytes: limits.maxArchiveBytes, signal });
1044
+ export async function writeZipArchive(archive, limits, signal, descriptors = false, forceZip64 = false, allowZip64 = true) {
1045
+ return collectBytes(streamZipArchive(archive, limits, signal, descriptors, forceZip64, allowZip64), { maxBytes: limits.maxArchiveBytes, signal });
648
1046
  }
649
1047
  //# sourceMappingURL=zip-format.js.map