@blamejs/core 0.7.4 → 0.7.18
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 +423 -395
- package/README.md +150 -149
- package/bin/blamejs.js +0 -0
- package/index.js +308 -284
- package/lib/api-key.js +660 -663
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt.js +311 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +594 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/codepoint-class.js +196 -0
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -286
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -421
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/file-upload.js +213 -10
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -215
- package/lib/framework-schema.js +717 -717
- package/lib/gate-contract.js +971 -0
- package/lib/guard-all.js +405 -0
- package/lib/guard-archive.js +739 -0
- package/lib/guard-csv.js +816 -0
- package/lib/guard-email.js +744 -0
- package/lib/guard-filename.js +724 -0
- package/lib/guard-html.js +976 -0
- package/lib/guard-json.js +729 -0
- package/lib/guard-markdown.js +586 -0
- package/lib/guard-svg.js +976 -0
- package/lib/guard-xml.js +405 -0
- package/lib/guard-yaml.js +529 -0
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -362
- package/lib/mail.js +981 -962
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/body-parser.js +1170 -1091
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +79 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -91
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +478 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/static.js +184 -4
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -319
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/archive.js
CHANGED
|
@@ -1,250 +1,250 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* archive — ZIP creation. Operator-data-export shape ("download my
|
|
4
|
-
* data as a zip"), log archives, plain-zip exports for users.
|
|
5
|
-
*
|
|
6
|
-
* var archive = b.archive.zip();
|
|
7
|
-
* archive.addFile("readme.txt", "Hello\n");
|
|
8
|
-
* archive.addFile("data/users.csv", csvBytes, { method: "deflate" });
|
|
9
|
-
* archive.addFile("avatars/me.png", pngBuf, { method: "store" }); // already-compressed
|
|
10
|
-
* var zipBytes = archive.toBuffer();
|
|
11
|
-
*
|
|
12
|
-
* // OR write directly to disk:
|
|
13
|
-
* archive.writeTo("/tmp/export.zip");
|
|
14
|
-
*
|
|
15
|
-
* Format support:
|
|
16
|
-
* - Stored (no compression — for already-compressed inputs like
|
|
17
|
-
* PNG / JPEG / mp4)
|
|
18
|
-
* - Deflate via node:zlib's deflateRawSync (default for everything else)
|
|
19
|
-
* - File names with / are honored — directory entries are implicit;
|
|
20
|
-
* extractors create the directory structure on demand
|
|
21
|
-
* - UTF-8 file names (sets the EFS bit per APPNOTE 6.3.4)
|
|
22
|
-
* - Modification time defaults to "now"; operators override per file
|
|
23
|
-
*
|
|
24
|
-
* Out of scope:
|
|
25
|
-
* - ZIP64 (>4 GiB archives, >65535 files) — operators at that scale
|
|
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
|
|
35
|
-
*/
|
|
36
|
-
var zlib = require("node:zlib");
|
|
37
|
-
var fs = require("node:fs");
|
|
38
|
-
var nodeCrypto = require("node:crypto");
|
|
39
|
-
var C = require("./constants");
|
|
40
|
-
var { defineClass } = require("./framework-error");
|
|
41
|
-
|
|
42
|
-
var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
43
|
-
|
|
44
|
-
// ZIP signatures (APPNOTE 4.3 — file-format-fixed magic dwords)
|
|
45
|
-
var SIG_LFH = 0x04034b50; // local file header
|
|
46
|
-
var SIG_CFH = 0x02014b50; // central directory file header
|
|
47
|
-
var SIG_EOCD = 0x06054b50; // end of central directory
|
|
48
|
-
|
|
49
|
-
// Compression methods (APPNOTE 4.4.5 — protocol-fixed method IDs)
|
|
50
|
-
var METHOD_STORE_ID = 0;
|
|
51
|
-
var METHOD_DEFLATE_ID = 8;
|
|
52
|
-
|
|
53
|
-
// CRC-32 — IEEE 802.3 polynomial. node:crypto has no native CRC32, so
|
|
54
|
-
// we vendor the standard table-driven implementation.
|
|
55
|
-
var CRC32_TABLE_LEN = 256; // entries (one per byte value)
|
|
56
|
-
var CRC32_BIT_ITER = 8; // bits per byte during table build
|
|
57
|
-
var CRC32_TABLE = (function () {
|
|
58
|
-
var t = new Uint32Array(CRC32_TABLE_LEN);
|
|
59
|
-
for (var i = 0; i < CRC32_TABLE_LEN; i++) {
|
|
60
|
-
var c = i;
|
|
61
|
-
for (var j = 0; j < CRC32_BIT_ITER; j++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
62
|
-
t[i] = c >>> 0;
|
|
63
|
-
}
|
|
64
|
-
return t;
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
function _crc32(buf) {
|
|
68
|
-
var crc = 0xffffffff;
|
|
69
|
-
for (var i = 0; i < buf.length; i++) {
|
|
70
|
-
crc = CRC32_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
71
|
-
}
|
|
72
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MS-DOS date/time encoding — APPNOTE 4.4.6
|
|
76
|
-
function _msdosDateTime(date) {
|
|
77
|
-
var d = date instanceof Date ? date : new Date(date);
|
|
78
|
-
if (isNaN(d.getTime())) d = new Date();
|
|
79
|
-
var dosTime = ((d.getHours() & 0x1f) << 11) |
|
|
80
|
-
((d.getMinutes() & 0x3f) << 5) |
|
|
81
|
-
((Math.floor(d.getSeconds() / 2)) & 0x1f);
|
|
82
|
-
var dosDate = (((d.getFullYear() - 1980) & 0x7f) << 9) |
|
|
83
|
-
(((d.getMonth() + 1) & 0xf) << 5) |
|
|
84
|
-
(d.getDate() & 0x1f);
|
|
85
|
-
return { time: dosTime, date: dosDate };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function zip() {
|
|
89
|
-
var entries = [];
|
|
90
|
-
|
|
91
|
-
function addFile(name, content, opts) {
|
|
92
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
93
|
-
throw new ArchiveError("archive/bad-name", "addFile: name must be a non-empty string");
|
|
94
|
-
}
|
|
95
|
-
if (name.indexOf("\0") !== -1) {
|
|
96
|
-
throw new ArchiveError("archive/bad-name", "addFile: name contains null byte");
|
|
97
|
-
}
|
|
98
|
-
// No path traversal — relative paths only, no leading slash, no ".." segments.
|
|
99
|
-
var normalized = name.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
100
|
-
var segs = normalized.split("/");
|
|
101
|
-
for (var si = 0; si < segs.length; si++) {
|
|
102
|
-
if (segs[si] === "..") {
|
|
103
|
-
throw new ArchiveError("archive/bad-name", "addFile: name contains '..' segment");
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
var bodyBuf;
|
|
107
|
-
if (Buffer.isBuffer(content)) bodyBuf = content;
|
|
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);
|
|
111
|
-
|
|
112
|
-
opts = opts || {};
|
|
113
|
-
var method = opts.method === "store" ? METHOD_STORE_ID : METHOD_DEFLATE_ID;
|
|
114
|
-
var mtime = opts.mtime instanceof Date ? opts.mtime : new Date();
|
|
115
|
-
|
|
116
|
-
var crc = _crc32(bodyBuf);
|
|
117
|
-
var stored = bodyBuf;
|
|
118
|
-
if (method === METHOD_DEFLATE_ID) {
|
|
119
|
-
stored = zlib.deflateRawSync(bodyBuf);
|
|
120
|
-
// If deflate didn't shrink it (small/already-compressed inputs),
|
|
121
|
-
// fall back to STORE to save the operator a few bytes.
|
|
122
|
-
if (stored.length >= bodyBuf.length) {
|
|
123
|
-
stored = bodyBuf;
|
|
124
|
-
method = METHOD_STORE_ID;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
entries.push({
|
|
129
|
-
name: normalized,
|
|
130
|
-
method: method,
|
|
131
|
-
mtime: mtime,
|
|
132
|
-
crc: crc,
|
|
133
|
-
stored: stored,
|
|
134
|
-
uncompressedSize: bodyBuf.length,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function _buildLocalFileHeader(entry) {
|
|
139
|
-
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
140
|
-
var dt = _msdosDateTime(entry.mtime);
|
|
141
|
-
// APPNOTE 4.3.7 — local file header. Offsets are byte positions
|
|
142
|
-
// within the 30-byte fixed header; each route through C.BYTES.bytes
|
|
143
|
-
// so the framework's byte-math discipline applies even to format-
|
|
144
|
-
// fixed offsets.
|
|
145
|
-
var hdr = Buffer.alloc(C.BYTES.bytes(30));
|
|
146
|
-
hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
|
|
147
|
-
hdr.writeUInt16LE(20, C.BYTES.bytes(4)); // version needed
|
|
148
|
-
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(6)); // flags: bit 11 = UTF-8 name
|
|
149
|
-
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
|
|
150
|
-
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
|
|
151
|
-
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));
|
|
155
|
-
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
|
|
156
|
-
hdr.writeUInt16LE(0, C.BYTES.bytes(28)); // extra field length
|
|
157
|
-
return Buffer.concat([hdr, nameBuf]);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function _buildCentralDirectoryEntry(entry, lfhOffset) {
|
|
161
|
-
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
162
|
-
var dt = _msdosDateTime(entry.mtime);
|
|
163
|
-
// APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
|
|
164
|
-
var hdr = Buffer.alloc(C.BYTES.bytes(46));
|
|
165
|
-
hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
|
|
166
|
-
hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
|
|
167
|
-
hdr.writeUInt16LE(20, C.BYTES.bytes(6)); // version needed
|
|
168
|
-
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(0x08)); // flags: bit 11 = UTF-8
|
|
169
|
-
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
|
|
170
|
-
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
|
|
171
|
-
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
|
|
172
|
-
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(0x10));
|
|
173
|
-
hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(20));
|
|
174
|
-
hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(0x18));
|
|
175
|
-
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(28));
|
|
176
|
-
hdr.writeUInt16LE(0, C.BYTES.bytes(30)); // extra field length
|
|
177
|
-
hdr.writeUInt16LE(0, C.BYTES.bytes(0x20)); // file comment length
|
|
178
|
-
hdr.writeUInt16LE(0, C.BYTES.bytes(34)); // disk number start
|
|
179
|
-
hdr.writeUInt16LE(0, C.BYTES.bytes(36)); // internal file attributes
|
|
180
|
-
hdr.writeUInt32LE(0, C.BYTES.bytes(38)); // external file attributes
|
|
181
|
-
hdr.writeUInt32LE(lfhOffset, C.BYTES.bytes(42));
|
|
182
|
-
return Buffer.concat([hdr, nameBuf]);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function toBuffer() {
|
|
186
|
-
if (entries.length > 65535) {
|
|
187
|
-
throw new ArchiveError("archive/too-many-entries",
|
|
188
|
-
"ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
|
|
189
|
-
}
|
|
190
|
-
var pieces = [];
|
|
191
|
-
var offsets = [];
|
|
192
|
-
var totalLocalBytes = 0;
|
|
193
|
-
for (var i = 0; i < entries.length; i++) {
|
|
194
|
-
offsets.push(totalLocalBytes);
|
|
195
|
-
var lfh = _buildLocalFileHeader(entries[i]);
|
|
196
|
-
pieces.push(lfh);
|
|
197
|
-
pieces.push(entries[i].stored);
|
|
198
|
-
totalLocalBytes += lfh.length + entries[i].stored.length;
|
|
199
|
-
}
|
|
200
|
-
var cdStart = totalLocalBytes;
|
|
201
|
-
var cdSize = 0;
|
|
202
|
-
for (var j = 0; j < entries.length; j++) {
|
|
203
|
-
var cdh = _buildCentralDirectoryEntry(entries[j], offsets[j]);
|
|
204
|
-
pieces.push(cdh);
|
|
205
|
-
cdSize += cdh.length;
|
|
206
|
-
}
|
|
207
|
-
// APPNOTE 4.3.16 — end of central directory record (22-byte fixed).
|
|
208
|
-
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
209
|
-
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
210
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(4)); // disk number
|
|
211
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(6)); // disk where CD starts
|
|
212
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08)); // entries on this disk
|
|
213
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10)); // total entries
|
|
214
|
-
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12)); // size of central directory
|
|
215
|
-
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10)); // offset of central directory
|
|
216
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(20)); // comment length
|
|
217
|
-
pieces.push(eocd);
|
|
218
|
-
return Buffer.concat(pieces);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function writeTo(filepath) {
|
|
222
|
-
var buf = toBuffer();
|
|
223
|
-
fs.writeFileSync(filepath, buf);
|
|
224
|
-
return buf.length;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function digest() {
|
|
228
|
-
// SHA3-512 of the produced archive bytes — useful for operator-side
|
|
229
|
-
// integrity logging on exported bundles. Matches the framework's
|
|
230
|
-
// PQC-first hash posture; operators reconciling against an external
|
|
231
|
-
// SHA-256 can hash the bytes themselves.
|
|
232
|
-
return nodeCrypto.createHash("sha3-512").update(toBuffer()).digest("hex");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
addFile: addFile,
|
|
237
|
-
toBuffer: toBuffer,
|
|
238
|
-
writeTo: writeTo,
|
|
239
|
-
digest: digest,
|
|
240
|
-
get entryCount() { return entries.length; },
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
module.exports = {
|
|
245
|
-
zip: zip,
|
|
246
|
-
ArchiveError: ArchiveError,
|
|
247
|
-
// Test-only export — operators don't call this; it's here for unit-testing
|
|
248
|
-
// the CRC implementation against known vectors.
|
|
249
|
-
_crc32ForTest: _crc32,
|
|
250
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* archive — ZIP creation. Operator-data-export shape ("download my
|
|
4
|
+
* data as a zip"), log archives, plain-zip exports for users.
|
|
5
|
+
*
|
|
6
|
+
* var archive = b.archive.zip();
|
|
7
|
+
* archive.addFile("readme.txt", "Hello\n");
|
|
8
|
+
* archive.addFile("data/users.csv", csvBytes, { method: "deflate" });
|
|
9
|
+
* archive.addFile("avatars/me.png", pngBuf, { method: "store" }); // already-compressed
|
|
10
|
+
* var zipBytes = archive.toBuffer();
|
|
11
|
+
*
|
|
12
|
+
* // OR write directly to disk:
|
|
13
|
+
* archive.writeTo("/tmp/export.zip");
|
|
14
|
+
*
|
|
15
|
+
* Format support:
|
|
16
|
+
* - Stored (no compression — for already-compressed inputs like
|
|
17
|
+
* PNG / JPEG / mp4)
|
|
18
|
+
* - Deflate via node:zlib's deflateRawSync (default for everything else)
|
|
19
|
+
* - File names with / are honored — directory entries are implicit;
|
|
20
|
+
* extractors create the directory structure on demand
|
|
21
|
+
* - UTF-8 file names (sets the EFS bit per APPNOTE 6.3.4)
|
|
22
|
+
* - Modification time defaults to "now"; operators override per file
|
|
23
|
+
*
|
|
24
|
+
* Out of scope:
|
|
25
|
+
* - ZIP64 (>4 GiB archives, >65535 files) — operators at that scale
|
|
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
|
|
35
|
+
*/
|
|
36
|
+
var zlib = require("node:zlib");
|
|
37
|
+
var fs = require("node:fs");
|
|
38
|
+
var nodeCrypto = require("node:crypto");
|
|
39
|
+
var C = require("./constants");
|
|
40
|
+
var { defineClass } = require("./framework-error");
|
|
41
|
+
|
|
42
|
+
var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
43
|
+
|
|
44
|
+
// ZIP signatures (APPNOTE 4.3 — file-format-fixed magic dwords)
|
|
45
|
+
var SIG_LFH = 0x04034b50; // local file header
|
|
46
|
+
var SIG_CFH = 0x02014b50; // central directory file header
|
|
47
|
+
var SIG_EOCD = 0x06054b50; // end of central directory
|
|
48
|
+
|
|
49
|
+
// Compression methods (APPNOTE 4.4.5 — protocol-fixed method IDs)
|
|
50
|
+
var METHOD_STORE_ID = 0;
|
|
51
|
+
var METHOD_DEFLATE_ID = 8;
|
|
52
|
+
|
|
53
|
+
// CRC-32 — IEEE 802.3 polynomial. node:crypto has no native CRC32, so
|
|
54
|
+
// we vendor the standard table-driven implementation.
|
|
55
|
+
var CRC32_TABLE_LEN = 256; // entries (one per byte value)
|
|
56
|
+
var CRC32_BIT_ITER = 8; // bits per byte during table build
|
|
57
|
+
var CRC32_TABLE = (function () {
|
|
58
|
+
var t = new Uint32Array(CRC32_TABLE_LEN);
|
|
59
|
+
for (var i = 0; i < CRC32_TABLE_LEN; i++) {
|
|
60
|
+
var c = i;
|
|
61
|
+
for (var j = 0; j < CRC32_BIT_ITER; j++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
62
|
+
t[i] = c >>> 0;
|
|
63
|
+
}
|
|
64
|
+
return t;
|
|
65
|
+
})();
|
|
66
|
+
|
|
67
|
+
function _crc32(buf) {
|
|
68
|
+
var crc = 0xffffffff;
|
|
69
|
+
for (var i = 0; i < buf.length; i++) {
|
|
70
|
+
crc = CRC32_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
71
|
+
}
|
|
72
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// MS-DOS date/time encoding — APPNOTE 4.4.6
|
|
76
|
+
function _msdosDateTime(date) {
|
|
77
|
+
var d = date instanceof Date ? date : new Date(date);
|
|
78
|
+
if (isNaN(d.getTime())) d = new Date();
|
|
79
|
+
var dosTime = ((d.getHours() & 0x1f) << 11) |
|
|
80
|
+
((d.getMinutes() & 0x3f) << 5) |
|
|
81
|
+
((Math.floor(d.getSeconds() / 2)) & 0x1f);
|
|
82
|
+
var dosDate = (((d.getFullYear() - 1980) & 0x7f) << 9) |
|
|
83
|
+
(((d.getMonth() + 1) & 0xf) << 5) |
|
|
84
|
+
(d.getDate() & 0x1f);
|
|
85
|
+
return { time: dosTime, date: dosDate };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function zip() {
|
|
89
|
+
var entries = [];
|
|
90
|
+
|
|
91
|
+
function addFile(name, content, opts) {
|
|
92
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
93
|
+
throw new ArchiveError("archive/bad-name", "addFile: name must be a non-empty string");
|
|
94
|
+
}
|
|
95
|
+
if (name.indexOf("\0") !== -1) {
|
|
96
|
+
throw new ArchiveError("archive/bad-name", "addFile: name contains null byte");
|
|
97
|
+
}
|
|
98
|
+
// No path traversal — relative paths only, no leading slash, no ".." segments.
|
|
99
|
+
var normalized = name.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
100
|
+
var segs = normalized.split("/");
|
|
101
|
+
for (var si = 0; si < segs.length; si++) {
|
|
102
|
+
if (segs[si] === "..") {
|
|
103
|
+
throw new ArchiveError("archive/bad-name", "addFile: name contains '..' segment");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
var bodyBuf;
|
|
107
|
+
if (Buffer.isBuffer(content)) bodyBuf = content;
|
|
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);
|
|
111
|
+
|
|
112
|
+
opts = opts || {};
|
|
113
|
+
var method = opts.method === "store" ? METHOD_STORE_ID : METHOD_DEFLATE_ID;
|
|
114
|
+
var mtime = opts.mtime instanceof Date ? opts.mtime : new Date();
|
|
115
|
+
|
|
116
|
+
var crc = _crc32(bodyBuf);
|
|
117
|
+
var stored = bodyBuf;
|
|
118
|
+
if (method === METHOD_DEFLATE_ID) {
|
|
119
|
+
stored = zlib.deflateRawSync(bodyBuf);
|
|
120
|
+
// If deflate didn't shrink it (small/already-compressed inputs),
|
|
121
|
+
// fall back to STORE to save the operator a few bytes.
|
|
122
|
+
if (stored.length >= bodyBuf.length) {
|
|
123
|
+
stored = bodyBuf;
|
|
124
|
+
method = METHOD_STORE_ID;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
entries.push({
|
|
129
|
+
name: normalized,
|
|
130
|
+
method: method,
|
|
131
|
+
mtime: mtime,
|
|
132
|
+
crc: crc,
|
|
133
|
+
stored: stored,
|
|
134
|
+
uncompressedSize: bodyBuf.length,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _buildLocalFileHeader(entry) {
|
|
139
|
+
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
140
|
+
var dt = _msdosDateTime(entry.mtime);
|
|
141
|
+
// APPNOTE 4.3.7 — local file header. Offsets are byte positions
|
|
142
|
+
// within the 30-byte fixed header; each route through C.BYTES.bytes
|
|
143
|
+
// so the framework's byte-math discipline applies even to format-
|
|
144
|
+
// fixed offsets.
|
|
145
|
+
var hdr = Buffer.alloc(C.BYTES.bytes(30));
|
|
146
|
+
hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
|
|
147
|
+
hdr.writeUInt16LE(20, C.BYTES.bytes(4)); // version needed
|
|
148
|
+
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(6)); // flags: bit 11 = UTF-8 name
|
|
149
|
+
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
|
|
150
|
+
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
|
|
151
|
+
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));
|
|
155
|
+
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
|
|
156
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(28)); // extra field length
|
|
157
|
+
return Buffer.concat([hdr, nameBuf]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _buildCentralDirectoryEntry(entry, lfhOffset) {
|
|
161
|
+
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
162
|
+
var dt = _msdosDateTime(entry.mtime);
|
|
163
|
+
// APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
|
|
164
|
+
var hdr = Buffer.alloc(C.BYTES.bytes(46));
|
|
165
|
+
hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
|
|
166
|
+
hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
|
|
167
|
+
hdr.writeUInt16LE(20, C.BYTES.bytes(6)); // version needed
|
|
168
|
+
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(0x08)); // flags: bit 11 = UTF-8
|
|
169
|
+
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
|
|
170
|
+
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
|
|
171
|
+
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
|
|
172
|
+
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(0x10));
|
|
173
|
+
hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(20));
|
|
174
|
+
hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(0x18));
|
|
175
|
+
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(28));
|
|
176
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(30)); // extra field length
|
|
177
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(0x20)); // file comment length
|
|
178
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(34)); // disk number start
|
|
179
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(36)); // internal file attributes
|
|
180
|
+
hdr.writeUInt32LE(0, C.BYTES.bytes(38)); // external file attributes
|
|
181
|
+
hdr.writeUInt32LE(lfhOffset, C.BYTES.bytes(42));
|
|
182
|
+
return Buffer.concat([hdr, nameBuf]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function toBuffer() {
|
|
186
|
+
if (entries.length > 65535) {
|
|
187
|
+
throw new ArchiveError("archive/too-many-entries",
|
|
188
|
+
"ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
|
|
189
|
+
}
|
|
190
|
+
var pieces = [];
|
|
191
|
+
var offsets = [];
|
|
192
|
+
var totalLocalBytes = 0;
|
|
193
|
+
for (var i = 0; i < entries.length; i++) {
|
|
194
|
+
offsets.push(totalLocalBytes);
|
|
195
|
+
var lfh = _buildLocalFileHeader(entries[i]);
|
|
196
|
+
pieces.push(lfh);
|
|
197
|
+
pieces.push(entries[i].stored);
|
|
198
|
+
totalLocalBytes += lfh.length + entries[i].stored.length;
|
|
199
|
+
}
|
|
200
|
+
var cdStart = totalLocalBytes;
|
|
201
|
+
var cdSize = 0;
|
|
202
|
+
for (var j = 0; j < entries.length; j++) {
|
|
203
|
+
var cdh = _buildCentralDirectoryEntry(entries[j], offsets[j]);
|
|
204
|
+
pieces.push(cdh);
|
|
205
|
+
cdSize += cdh.length;
|
|
206
|
+
}
|
|
207
|
+
// APPNOTE 4.3.16 — end of central directory record (22-byte fixed).
|
|
208
|
+
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
209
|
+
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
210
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(4)); // disk number
|
|
211
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(6)); // disk where CD starts
|
|
212
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08)); // entries on this disk
|
|
213
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10)); // total entries
|
|
214
|
+
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12)); // size of central directory
|
|
215
|
+
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10)); // offset of central directory
|
|
216
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(20)); // comment length
|
|
217
|
+
pieces.push(eocd);
|
|
218
|
+
return Buffer.concat(pieces);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function writeTo(filepath) {
|
|
222
|
+
var buf = toBuffer();
|
|
223
|
+
fs.writeFileSync(filepath, buf);
|
|
224
|
+
return buf.length;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function digest() {
|
|
228
|
+
// SHA3-512 of the produced archive bytes — useful for operator-side
|
|
229
|
+
// integrity logging on exported bundles. Matches the framework's
|
|
230
|
+
// PQC-first hash posture; operators reconciling against an external
|
|
231
|
+
// SHA-256 can hash the bytes themselves.
|
|
232
|
+
return nodeCrypto.createHash("sha3-512").update(toBuffer()).digest("hex");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
addFile: addFile,
|
|
237
|
+
toBuffer: toBuffer,
|
|
238
|
+
writeTo: writeTo,
|
|
239
|
+
digest: digest,
|
|
240
|
+
get entryCount() { return entries.length; },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
zip: zip,
|
|
246
|
+
ArchiveError: ArchiveError,
|
|
247
|
+
// Test-only export — operators don't call this; it's here for unit-testing
|
|
248
|
+
// the CRC implementation against known vectors.
|
|
249
|
+
_crc32ForTest: _crc32,
|
|
250
|
+
};
|