@blamejs/core 0.8.43 → 0.8.49
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.
- package/CHANGELOG.md +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/archive.js
CHANGED
|
@@ -1,41 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* @module b.archive
|
|
4
|
+
* @nav Tools
|
|
5
|
+
* @title Archive
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* archive.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* ZIP archive creation primitive. Operator-data-export shape
|
|
9
|
+
* ("download my data as a zip"), log bundling, plain-zip exports for
|
|
10
|
+
* end users.
|
|
11
|
+
*
|
|
12
|
+
* Two output paths:
|
|
13
|
+
* - `toBuffer()` builds the whole archive in memory — good for
|
|
14
|
+
* small-to-medium exports that fit comfortably in process RSS.
|
|
15
|
+
* - `toStream(writable)` deflates each entry through a piped
|
|
16
|
+
* zlib transform and writes the central directory only after
|
|
17
|
+
* every entry finalizes, so multi-GB exports never need to
|
|
18
|
+
* fit in memory. If any source errors mid-pipe, the destination
|
|
19
|
+
* is destroyed with `archive/aborted` — consumers see a broken
|
|
20
|
+
* stream rather than a half-archive that pretends to be complete.
|
|
21
|
+
*
|
|
22
|
+
* Compression:
|
|
23
|
+
* - `deflate` (default) via node:zlib's `deflateRawSync` —
|
|
24
|
+
* falls back to STORE when deflate didn't shrink the input.
|
|
25
|
+
* - `store` — no compression, for already-compressed bytes
|
|
26
|
+
* (PNG / JPEG / mp4).
|
|
11
27
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
28
|
+
* Format guarantees:
|
|
29
|
+
* - Deterministic insertion order (entries appear in the order
|
|
30
|
+
* `addFile` is called; central directory matches).
|
|
31
|
+
* - UTF-8 file names with the APPNOTE 6.3.4 EFS bit set.
|
|
32
|
+
* - Path-traversal refused at `addFile`: leading `/`, backslashes,
|
|
33
|
+
* null bytes, and `..` segments throw `archive/bad-name`.
|
|
34
|
+
* - No symlink emission — only regular file entries are produced.
|
|
35
|
+
* - SHA3-512 fingerprint via `digest()` for operator integrity logs.
|
|
14
36
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
37
|
+
* Out of scope (v1):
|
|
38
|
+
* - ZIP64 (>4 GiB archives, >65535 files) — `toBuffer` and
|
|
39
|
+
* `toStream` throw `archive/too-many-entries` past the limit;
|
|
40
|
+
* operators at that scale bring their own toolset.
|
|
41
|
+
* - ZIP-native password encryption (broken-by-design); operators
|
|
42
|
+
* wrap the produced bytes via `b.crypto.encryptPacked` for
|
|
43
|
+
* encryption-at-rest.
|
|
44
|
+
* - Reading / extraction — write-only; operators use yauzl or
|
|
45
|
+
* `unzip` for read paths.
|
|
23
46
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* bring their own toolset
|
|
27
|
-
* - Encryption — `b.crypto.encryptPacked` produces a sealed bundle
|
|
28
|
-
* for the operator's encryption-at-rest needs; ZIP-native
|
|
29
|
-
* password encryption is broken-by-design
|
|
30
|
-
* - Streaming write (toStream) — toBuffer() covers the "download my
|
|
31
|
-
* data" shape; operators streaming gigabytes use the operator-side
|
|
32
|
-
* toolset
|
|
33
|
-
* - Reading / extraction — write-only; operators use node:zlib +
|
|
34
|
-
* yauzl (or unzip in shell) for read paths
|
|
47
|
+
* @card
|
|
48
|
+
* ZIP archive creation primitive.
|
|
35
49
|
*/
|
|
36
50
|
var zlib = require("node:zlib");
|
|
37
51
|
var fs = require("node:fs");
|
|
38
52
|
var nodeCrypto = require("node:crypto");
|
|
53
|
+
var stream = require("node:stream");
|
|
54
|
+
var streamPromises = require("node:stream/promises");
|
|
39
55
|
var C = require("./constants");
|
|
40
56
|
var { defineClass } = require("./framework-error");
|
|
41
57
|
|
|
@@ -85,10 +101,43 @@ function _msdosDateTime(date) {
|
|
|
85
101
|
return { time: dosTime, date: dosDate };
|
|
86
102
|
}
|
|
87
103
|
|
|
104
|
+
/**
|
|
105
|
+
* @primitive b.archive.zip
|
|
106
|
+
* @signature b.archive.zip()
|
|
107
|
+
* @since 0.4.0
|
|
108
|
+
* @status stable
|
|
109
|
+
* @related b.crypto.encryptPacked
|
|
110
|
+
*
|
|
111
|
+
* Create a new ZIP archive builder. The returned object exposes
|
|
112
|
+
* `addFile(name, content, opts?)`, `toBuffer()`, `toStream(writable?)`,
|
|
113
|
+
* `writeTo(path)`, `digest()`, and `entryCount`. Entries appear in the
|
|
114
|
+
* archive's central directory in insertion order — same byte output
|
|
115
|
+
* given the same input sequence and mtimes.
|
|
116
|
+
*
|
|
117
|
+
* `content` may be a `Buffer`, a UTF-8 `string`, or a `Readable`; only
|
|
118
|
+
* `toStream()` can finalize archives containing `Readable` sources
|
|
119
|
+
* (`toBuffer()` throws `archive/streaming-entry`).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* var archive = b.archive.zip();
|
|
123
|
+
* archive.addFile("readme.txt", "Hello\n");
|
|
124
|
+
* archive.addFile("data/users.csv", Buffer.from("name,age\nAda,36\n"));
|
|
125
|
+
* archive.addFile("avatars/me.png", Buffer.from([0x89, 0x50, 0x4e, 0x47]),
|
|
126
|
+
* { method: "store" }); // already-compressed
|
|
127
|
+
* var zipBytes = archive.toBuffer();
|
|
128
|
+
* archive.entryCount; // → 3
|
|
129
|
+
* typeof archive.digest(); // → "string" (sha3-512 hex)
|
|
130
|
+
*
|
|
131
|
+
* // Stream a multi-GB export directly to an HTTP response.
|
|
132
|
+
* var fs = require("node:fs");
|
|
133
|
+
* var big = b.archive.zip();
|
|
134
|
+
* big.addFile("logs/2026-q1.ndjson", fs.createReadStream("/var/log/q1.ndjson"));
|
|
135
|
+
* // await big.toStream(res);
|
|
136
|
+
*/
|
|
88
137
|
function zip() {
|
|
89
138
|
var entries = [];
|
|
90
139
|
|
|
91
|
-
function
|
|
140
|
+
function _normalizeName(name) {
|
|
92
141
|
if (typeof name !== "string" || name.length === 0) {
|
|
93
142
|
throw new ArchiveError("archive/bad-name", "addFile: name must be a non-empty string");
|
|
94
143
|
}
|
|
@@ -103,16 +152,42 @@ function zip() {
|
|
|
103
152
|
throw new ArchiveError("archive/bad-name", "addFile: name contains '..' segment");
|
|
104
153
|
}
|
|
105
154
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
else if (typeof content === "string") bodyBuf = Buffer.from(content, "utf8");
|
|
109
|
-
else throw new ArchiveError("archive/bad-content",
|
|
110
|
-
"addFile: content must be a Buffer or string, got " + typeof content);
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
111
157
|
|
|
158
|
+
function _isReadableStream(o) {
|
|
159
|
+
// Duck-type: Readable instance or any object exposing .pipe + .on +
|
|
160
|
+
// a `readable` flag / readableState. Avoids importing every consumer's
|
|
161
|
+
// stream class; matches Node's own stream-detection pattern.
|
|
162
|
+
return !!o && (o instanceof stream.Readable ||
|
|
163
|
+
(typeof o.pipe === "function" && typeof o.on === "function"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function addFile(name, content, opts) {
|
|
167
|
+
var normalized = _normalizeName(name);
|
|
112
168
|
opts = opts || {};
|
|
113
169
|
var method = opts.method === "store" ? METHOD_STORE_ID : METHOD_DEFLATE_ID;
|
|
114
170
|
var mtime = opts.mtime instanceof Date ? opts.mtime : new Date();
|
|
115
171
|
|
|
172
|
+
if (_isReadableStream(content)) {
|
|
173
|
+
// Streaming entry — content is finalized at toStream() time. CRC,
|
|
174
|
+
// sizes, and method-fallback-to-STORE are computed during the pipe.
|
|
175
|
+
entries.push({
|
|
176
|
+
name: normalized,
|
|
177
|
+
method: method,
|
|
178
|
+
mtime: mtime,
|
|
179
|
+
source: content,
|
|
180
|
+
kind: "stream",
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
var bodyBuf;
|
|
186
|
+
if (Buffer.isBuffer(content)) bodyBuf = content;
|
|
187
|
+
else if (typeof content === "string") bodyBuf = Buffer.from(content, "utf8");
|
|
188
|
+
else throw new ArchiveError("archive/bad-content",
|
|
189
|
+
"addFile: content must be a Buffer, string, or Readable, got " + typeof content);
|
|
190
|
+
|
|
116
191
|
var crc = _crc32(bodyBuf);
|
|
117
192
|
var stored = bodyBuf;
|
|
118
193
|
if (method === METHOD_DEFLATE_ID) {
|
|
@@ -132,12 +207,22 @@ function zip() {
|
|
|
132
207
|
crc: crc,
|
|
133
208
|
stored: stored,
|
|
134
209
|
uncompressedSize: bodyBuf.length,
|
|
210
|
+
kind: "buffer",
|
|
135
211
|
});
|
|
136
212
|
}
|
|
137
213
|
|
|
138
|
-
|
|
214
|
+
// FLAG_DATA_DESCRIPTOR — APPNOTE 4.4.4 bit 3. Set when crc / csize /
|
|
215
|
+
// usize are not known at LFH-write time and a 16-byte descriptor
|
|
216
|
+
// follows the compressed data.
|
|
217
|
+
var FLAG_UTF8_NAME = 0x0800;
|
|
218
|
+
var FLAG_DATA_DESCRIPTOR = 0x0008;
|
|
219
|
+
var SIG_DATA_DESCRIPTOR = 0x08074b50; // optional but conventional
|
|
220
|
+
|
|
221
|
+
function _buildLocalFileHeader(entry, opts) {
|
|
222
|
+
var streaming = !!(opts && opts.streaming);
|
|
139
223
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
140
224
|
var dt = _msdosDateTime(entry.mtime);
|
|
225
|
+
var flags = FLAG_UTF8_NAME | (streaming ? FLAG_DATA_DESCRIPTOR : 0);
|
|
141
226
|
// APPNOTE 4.3.7 — local file header. Offsets are byte positions
|
|
142
227
|
// within the 30-byte fixed header; each route through C.BYTES.bytes
|
|
143
228
|
// so the framework's byte-math discipline applies even to format-
|
|
@@ -145,27 +230,38 @@ function zip() {
|
|
|
145
230
|
var hdr = Buffer.alloc(C.BYTES.bytes(30));
|
|
146
231
|
hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
|
|
147
232
|
hdr.writeUInt16LE(20, C.BYTES.bytes(4)); // version needed
|
|
148
|
-
hdr.writeUInt16LE(
|
|
233
|
+
hdr.writeUInt16LE(flags, C.BYTES.bytes(6)); // flags: bit 11 UTF-8, bit 3 data-descriptor
|
|
149
234
|
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
|
|
150
235
|
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
|
|
151
236
|
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(12));
|
|
152
|
-
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(14));
|
|
153
|
-
hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(18));
|
|
154
|
-
hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(22));
|
|
237
|
+
hdr.writeUInt32LE(streaming ? 0 : entry.crc, C.BYTES.bytes(14));
|
|
238
|
+
hdr.writeUInt32LE(streaming ? 0 : entry.stored.length, C.BYTES.bytes(18));
|
|
239
|
+
hdr.writeUInt32LE(streaming ? 0 : entry.uncompressedSize, C.BYTES.bytes(22));
|
|
155
240
|
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
|
|
156
241
|
hdr.writeUInt16LE(0, C.BYTES.bytes(28)); // extra field length
|
|
157
242
|
return Buffer.concat([hdr, nameBuf]);
|
|
158
243
|
}
|
|
159
244
|
|
|
245
|
+
function _buildDataDescriptor(crc, csize, usize) {
|
|
246
|
+
// APPNOTE 4.3.9 — 16-byte data descriptor (with optional sig dword).
|
|
247
|
+
var dd = Buffer.alloc(C.BYTES.bytes(16));
|
|
248
|
+
dd.writeUInt32LE(SIG_DATA_DESCRIPTOR, C.BYTES.bytes(0));
|
|
249
|
+
dd.writeUInt32LE(crc, C.BYTES.bytes(4));
|
|
250
|
+
dd.writeUInt32LE(csize, C.BYTES.bytes(0x08));
|
|
251
|
+
dd.writeUInt32LE(usize, C.BYTES.bytes(12));
|
|
252
|
+
return dd;
|
|
253
|
+
}
|
|
254
|
+
|
|
160
255
|
function _buildCentralDirectoryEntry(entry, lfhOffset) {
|
|
161
256
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
162
257
|
var dt = _msdosDateTime(entry.mtime);
|
|
258
|
+
var flags = FLAG_UTF8_NAME | (entry.kind === "stream" ? FLAG_DATA_DESCRIPTOR : 0);
|
|
163
259
|
// APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
|
|
164
260
|
var hdr = Buffer.alloc(C.BYTES.bytes(46));
|
|
165
261
|
hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
|
|
166
262
|
hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
|
|
167
263
|
hdr.writeUInt16LE(20, C.BYTES.bytes(6)); // version needed
|
|
168
|
-
hdr.writeUInt16LE(
|
|
264
|
+
hdr.writeUInt16LE(flags, C.BYTES.bytes(0x08)); // flags: bit 11 UTF-8, bit 3 data-descriptor (stream)
|
|
169
265
|
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
|
|
170
266
|
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
|
|
171
267
|
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
|
|
@@ -187,6 +283,13 @@ function zip() {
|
|
|
187
283
|
throw new ArchiveError("archive/too-many-entries",
|
|
188
284
|
"ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
|
|
189
285
|
}
|
|
286
|
+
for (var k = 0; k < entries.length; k++) {
|
|
287
|
+
if (entries[k].kind === "stream") {
|
|
288
|
+
throw new ArchiveError("archive/streaming-entry",
|
|
289
|
+
"toBuffer cannot finalize streaming entry " + JSON.stringify(entries[k].name) +
|
|
290
|
+
"; use archive.toStream(writable) for archives containing Readable sources");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
190
293
|
var pieces = [];
|
|
191
294
|
var offsets = [];
|
|
192
295
|
var totalLocalBytes = 0;
|
|
@@ -224,6 +327,199 @@ function zip() {
|
|
|
224
327
|
return buf.length;
|
|
225
328
|
}
|
|
226
329
|
|
|
330
|
+
function _emitAudit(opts, action, outcome, metadata) {
|
|
331
|
+
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
332
|
+
try {
|
|
333
|
+
opts.audit.safeEmit({
|
|
334
|
+
action: action,
|
|
335
|
+
outcome: outcome,
|
|
336
|
+
metadata: metadata,
|
|
337
|
+
});
|
|
338
|
+
} catch (_e) { /* drop-silent — audit sinks must never crash the producer */ }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function _writeChunk(writable, chunk) {
|
|
342
|
+
return new Promise(function (resolve, reject) {
|
|
343
|
+
function onError(e) {
|
|
344
|
+
writable.removeListener("drain", onDrain);
|
|
345
|
+
reject(e);
|
|
346
|
+
}
|
|
347
|
+
function onDrain() {
|
|
348
|
+
writable.removeListener("error", onError);
|
|
349
|
+
resolve();
|
|
350
|
+
}
|
|
351
|
+
var ok = writable.write(chunk);
|
|
352
|
+
if (ok) {
|
|
353
|
+
// Already flushed — no need to wait for drain.
|
|
354
|
+
resolve();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
writable.once("drain", onDrain);
|
|
358
|
+
writable.once("error", onError);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function _streamEntry(entry, writable) {
|
|
363
|
+
var lfh = _buildLocalFileHeader(entry, { streaming: true });
|
|
364
|
+
await _writeChunk(writable, lfh);
|
|
365
|
+
|
|
366
|
+
var crc = 0xffffffff;
|
|
367
|
+
var usize = 0;
|
|
368
|
+
var csize = 0;
|
|
369
|
+
var method = entry.method;
|
|
370
|
+
|
|
371
|
+
// Per-byte CRC table walk reused from _crc32 (kept inline here so
|
|
372
|
+
// we operate on Buffer chunks rather than allocating a full body).
|
|
373
|
+
function _crcChunk(chunk) {
|
|
374
|
+
for (var i = 0; i < chunk.length; i++) {
|
|
375
|
+
crc = CRC32_TABLE[(crc ^ chunk[i]) & 0xff] ^ (crc >>> 8);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// CRC tap — a passthrough Transform that observes uncompressed bytes
|
|
380
|
+
// and updates usize / CRC before forwarding to the next stage. Avoids
|
|
381
|
+
// consuming the source twice via parallel listeners.
|
|
382
|
+
var crcTap = new stream.Transform({
|
|
383
|
+
transform: function (chunk, enc, cb) {
|
|
384
|
+
usize += chunk.length;
|
|
385
|
+
_crcChunk(chunk);
|
|
386
|
+
cb(null, chunk);
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (method === METHOD_DEFLATE_ID) {
|
|
391
|
+
var deflater = zlib.createDeflateRaw();
|
|
392
|
+
// Pipe source -> crcTap -> deflater -> writable directly. CRC is
|
|
393
|
+
// computed on uncompressed bytes via the tap. Atomicity is held by
|
|
394
|
+
// the central-directory-after-all-entries pattern: partial bytes
|
|
395
|
+
// may leak to dest on failure, but no EOCD is ever written, so
|
|
396
|
+
// consumers see a broken stream rather than a half-archive that
|
|
397
|
+
// pretends to be complete.
|
|
398
|
+
var sinkWritable = new stream.Writable({
|
|
399
|
+
write: function (chunk, enc, cb) {
|
|
400
|
+
csize += chunk.length;
|
|
401
|
+
var ok = writable.write(chunk);
|
|
402
|
+
if (ok) cb();
|
|
403
|
+
else writable.once("drain", function () { cb(); });
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
try {
|
|
407
|
+
await streamPromises.pipeline(entry.source, crcTap, deflater, sinkWritable);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
throw new ArchiveError("archive/source-error",
|
|
410
|
+
"stream entry " + JSON.stringify(entry.name) + " failed: " + (e && e.message));
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
// STORE: pipe source -> crcTap -> writable. csize === usize.
|
|
414
|
+
var storeCollect = new stream.Writable({
|
|
415
|
+
write: function (chunk, enc, cb) {
|
|
416
|
+
csize += chunk.length;
|
|
417
|
+
var ok = writable.write(chunk);
|
|
418
|
+
if (ok) cb();
|
|
419
|
+
else writable.once("drain", function () { cb(); });
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
try {
|
|
423
|
+
await streamPromises.pipeline(entry.source, crcTap, storeCollect);
|
|
424
|
+
} catch (e) {
|
|
425
|
+
throw new ArchiveError("archive/source-error",
|
|
426
|
+
"stream entry " + JSON.stringify(entry.name) + " failed: " + (e && e.message));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
crc = (crc ^ 0xffffffff) >>> 0;
|
|
431
|
+
var dd = _buildDataDescriptor(crc, csize, usize);
|
|
432
|
+
await _writeChunk(writable, dd);
|
|
433
|
+
|
|
434
|
+
// Mutate entry with finalized values so the central-directory
|
|
435
|
+
// build pass writes correct sizes/crc.
|
|
436
|
+
entry.crc = crc;
|
|
437
|
+
entry.stored = { length: csize }; // CDH only reads .length
|
|
438
|
+
entry.uncompressedSize = usize;
|
|
439
|
+
|
|
440
|
+
return lfh.length + csize + dd.length;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function toStream(writable, opts) {
|
|
444
|
+
opts = opts || {};
|
|
445
|
+
var returnReadable = !writable;
|
|
446
|
+
var dest = writable;
|
|
447
|
+
if (returnReadable) {
|
|
448
|
+
dest = new stream.PassThrough();
|
|
449
|
+
} else if (typeof writable.write !== "function") {
|
|
450
|
+
throw new ArchiveError("archive/bad-writable",
|
|
451
|
+
"toStream: writable must be a Writable (or omit to receive a Readable)");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (entries.length > 65535) {
|
|
455
|
+
throw new ArchiveError("archive/too-many-entries",
|
|
456
|
+
"ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
var run = (async function () {
|
|
460
|
+
var offsets = [];
|
|
461
|
+
var totalLocalBytes = 0;
|
|
462
|
+
try {
|
|
463
|
+
for (var i = 0; i < entries.length; i++) {
|
|
464
|
+
offsets.push(totalLocalBytes);
|
|
465
|
+
var entry = entries[i];
|
|
466
|
+
if (entry.kind === "stream") {
|
|
467
|
+
totalLocalBytes += await _streamEntry(entry, dest);
|
|
468
|
+
} else {
|
|
469
|
+
var lfh = _buildLocalFileHeader(entry);
|
|
470
|
+
await _writeChunk(dest, lfh);
|
|
471
|
+
await _writeChunk(dest, entry.stored);
|
|
472
|
+
totalLocalBytes += lfh.length + entry.stored.length;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Atomic finalize — central directory only after every entry succeeded.
|
|
476
|
+
var cdStart = totalLocalBytes;
|
|
477
|
+
var cdSize = 0;
|
|
478
|
+
for (var j = 0; j < entries.length; j++) {
|
|
479
|
+
var cdh = _buildCentralDirectoryEntry(entries[j], offsets[j]);
|
|
480
|
+
await _writeChunk(dest, cdh);
|
|
481
|
+
cdSize += cdh.length;
|
|
482
|
+
}
|
|
483
|
+
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
484
|
+
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
485
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(4));
|
|
486
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(6));
|
|
487
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08));
|
|
488
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10));
|
|
489
|
+
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12));
|
|
490
|
+
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10));
|
|
491
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(20));
|
|
492
|
+
await _writeChunk(dest, eocd);
|
|
493
|
+
if (typeof dest.end === "function") dest.end();
|
|
494
|
+
_emitAudit(opts, "archive.zip.streamed.completed", "success", {
|
|
495
|
+
entries: entries.length,
|
|
496
|
+
bytes: totalLocalBytes + cdSize + eocd.length,
|
|
497
|
+
});
|
|
498
|
+
} catch (e) {
|
|
499
|
+
// Aborted — destroy the destination so partial output is not
|
|
500
|
+
// mistaken for a complete archive (no central directory written).
|
|
501
|
+
_emitAudit(opts, "archive.zip.streamed.aborted", "failure", {
|
|
502
|
+
entries: entries.length,
|
|
503
|
+
error: e && (e.code || e.message) || String(e),
|
|
504
|
+
});
|
|
505
|
+
if (typeof dest.destroy === "function") {
|
|
506
|
+
dest.destroy(e instanceof ArchiveError ? e : new ArchiveError(
|
|
507
|
+
"archive/aborted", "archive stream aborted: " + (e && e.message || e)));
|
|
508
|
+
}
|
|
509
|
+
if (!returnReadable) throw e;
|
|
510
|
+
}
|
|
511
|
+
})();
|
|
512
|
+
|
|
513
|
+
if (returnReadable) {
|
|
514
|
+
// Operator gets a Readable; surface stream errors via the
|
|
515
|
+
// PassThrough's 'error' event (run() does not throw in that path).
|
|
516
|
+
run.catch(function () { /* already routed via dest.destroy */ });
|
|
517
|
+
return dest;
|
|
518
|
+
}
|
|
519
|
+
await run;
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
227
523
|
function digest() {
|
|
228
524
|
// SHA3-512 of the produced archive bytes — useful for operator-side
|
|
229
525
|
// integrity logging on exported bundles. Matches the framework's
|
|
@@ -235,6 +531,7 @@ function zip() {
|
|
|
235
531
|
return {
|
|
236
532
|
addFile: addFile,
|
|
237
533
|
toBuffer: toBuffer,
|
|
534
|
+
toStream: toStream,
|
|
238
535
|
writeTo: writeTo,
|
|
239
536
|
digest: digest,
|
|
240
537
|
get entryCount() { return entries.length; },
|