@blamejs/core 0.15.12 → 0.15.14
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 +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/structured-fields.js
CHANGED
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
// node:util is a builtin (no lib require cycle) — used for strict UTF-8
|
|
71
71
|
// validation of RFC 9651 Display Strings.
|
|
72
72
|
var TextDecoder = require("node:util").TextDecoder;
|
|
73
|
+
var codepointClass = require("./codepoint-class");
|
|
73
74
|
|
|
74
75
|
function splitTopLevel(s, sep) {
|
|
75
76
|
if (typeof s !== "string") return [];
|
|
@@ -103,6 +104,261 @@ function splitTopLevel(s, sep) {
|
|
|
103
104
|
return out;
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @primitive b.structuredFields.splitUnquoted
|
|
109
|
+
* @signature b.structuredFields.splitUnquoted(s, sep)
|
|
110
|
+
* @since 0.15.13
|
|
111
|
+
* @status stable
|
|
112
|
+
* @related b.structuredFields.splitTopLevel
|
|
113
|
+
*
|
|
114
|
+
* Split `s` on every `sep` that falls OUTSIDE a `"..."` quoted run — the
|
|
115
|
+
* iCalendar / vCard variant of the quote-aware splitter. A `"` toggles the
|
|
116
|
+
* quoted state and there is NO backslash escaping of the quote (RFC 5545
|
|
117
|
+
* 3.1.1 / RFC 6350 3.3 QSAFE-CHAR excludes DQUOTE, so a `"` always opens or
|
|
118
|
+
* closes a run). This is deliberately simpler than `splitTopLevel`, which
|
|
119
|
+
* honours HTTP structured-field backslash-quote escapes; the two are NOT
|
|
120
|
+
* interchangeable. Accepts any single-character separator.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* b.structuredFields.splitUnquoted('a;b="x;y";c', ";");
|
|
124
|
+
* // returns ['a', 'b="x;y"', 'c']
|
|
125
|
+
*/
|
|
126
|
+
function splitUnquoted(s, sep) {
|
|
127
|
+
var out = [];
|
|
128
|
+
var inQ = false;
|
|
129
|
+
var start = 0;
|
|
130
|
+
for (var i = 0; i < s.length; i++) {
|
|
131
|
+
var c = s.charAt(i);
|
|
132
|
+
if (c === '"') { inQ = !inQ; continue; }
|
|
133
|
+
if (c === sep && !inQ) {
|
|
134
|
+
out.push(s.slice(start, i));
|
|
135
|
+
start = i + 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
out.push(s.slice(start));
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @primitive b.structuredFields.stripDoubleQuotes
|
|
144
|
+
* @signature b.structuredFields.stripDoubleQuotes(s)
|
|
145
|
+
* @since 0.15.13
|
|
146
|
+
* @status stable
|
|
147
|
+
* @related b.structuredFields.splitUnquoted, b.structuredFields.unquoteSfString
|
|
148
|
+
*
|
|
149
|
+
* Strip ONE layer of surrounding `"` from `s` when both ends are a double
|
|
150
|
+
* quote (length ≥ 2), otherwise return `s` unchanged. The plain DQUOTE
|
|
151
|
+
* unwrap used by the iCal / vCard parsers for a quoted parameter value —
|
|
152
|
+
* no backslash-escape processing (RFC 5545 3.1 / RFC 6350 5: a quoted
|
|
153
|
+
* param value is QSAFE-CHAR, which excludes DQUOTE, so there is nothing to
|
|
154
|
+
* unescape). Distinct from `unquoteSfString`, which decodes HTTP
|
|
155
|
+
* structured-field `\"` / `\\` escapes.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* b.structuredFields.stripDoubleQuotes('"a;b"');
|
|
159
|
+
* // returns 'a;b'
|
|
160
|
+
*
|
|
161
|
+
* b.structuredFields.stripDoubleQuotes('plain');
|
|
162
|
+
* // returns 'plain'
|
|
163
|
+
*/
|
|
164
|
+
function stripDoubleQuotes(s) {
|
|
165
|
+
if (s.length >= 2 && s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') {
|
|
166
|
+
return s.slice(1, -1);
|
|
167
|
+
}
|
|
168
|
+
return s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @primitive b.structuredFields.unfoldHeaderContinuations
|
|
173
|
+
* @signature b.structuredFields.unfoldHeaderContinuations(value)
|
|
174
|
+
* @since 0.15.13
|
|
175
|
+
* @status stable
|
|
176
|
+
* @related b.structuredFields.parseTagList
|
|
177
|
+
*
|
|
178
|
+
* Collapse RFC 5322 folding whitespace — a CRLF (or bare LF) followed by one
|
|
179
|
+
* or more spaces/tabs — back to a single space, reversing the line folding a
|
|
180
|
+
* header value may carry in transit. Used before parsing DKIM / ARC /
|
|
181
|
+
* Authentication-Results tag lists, where a folded `b=` / `bh=` value must be
|
|
182
|
+
* rejoined before its base64 is read.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* b.structuredFields.unfoldHeaderContinuations("v=DKIM1;\r\n k=rsa");
|
|
186
|
+
* // returns "v=DKIM1; k=rsa"
|
|
187
|
+
*/
|
|
188
|
+
function unfoldHeaderContinuations(value) {
|
|
189
|
+
return String(value).replace(/\r?\n[ \t]+/g, " ");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @primitive b.structuredFields.parseKeyValuePiece
|
|
194
|
+
* @signature b.structuredFields.parseKeyValuePiece(piece, kvSep?, lowerKey?)
|
|
195
|
+
* @since 0.15.13
|
|
196
|
+
* @status stable
|
|
197
|
+
* @related b.structuredFields.parseTagList, b.structuredFields.splitTopLevel
|
|
198
|
+
*
|
|
199
|
+
* Parse ONE already-split list piece into a `{ key, value }` pair: `key` is the
|
|
200
|
+
* text before the first `kvSep` (default "="), trimmed and — unless `lowerKey`
|
|
201
|
+
* is `false` — lower-cased; `value` is the raw remainder (the caller trims /
|
|
202
|
+
* unquotes / sf-string-parses it per its own grammar). A piece with NO `kvSep`
|
|
203
|
+
* is a "bare" item — `value` is null — which the caller handles per its grammar
|
|
204
|
+
* (skip it, or treat as a flag-style directive).
|
|
205
|
+
*
|
|
206
|
+
* The shared per-pair step behind every `key=value` list parser: the naive
|
|
207
|
+
* `parseTagList` loop AND the quote-aware parsers that first `splitTopLevel`
|
|
208
|
+
* then parse each piece (Cache-Control directives, Client-Hints brand params,
|
|
209
|
+
* Content-Type parameters). `lowerKey:false` serves the rare grammar whose key
|
|
210
|
+
* is case-sensitive (a verbatim tag-list passthrough).
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* b.structuredFields.parseKeyValuePiece("Max-Age=60"); // → { key: "max-age", value: "60" }
|
|
214
|
+
* b.structuredFields.parseKeyValuePiece("no-store"); // → { key: "no-store", value: null }
|
|
215
|
+
* b.structuredFields.parseKeyValuePiece("Key=a=b", "=", false); // → { key: "Key", value: "a=b" }
|
|
216
|
+
*/
|
|
217
|
+
function parseKeyValuePiece(piece, kvSep, lowerKey) {
|
|
218
|
+
var sep = kvSep || "=";
|
|
219
|
+
var at = piece.indexOf(sep);
|
|
220
|
+
var rawKey = (at === -1 ? piece : piece.slice(0, at)).trim();
|
|
221
|
+
var key = lowerKey === false ? rawKey : rawKey.toLowerCase();
|
|
222
|
+
return { key: key, value: at === -1 ? null : piece.slice(at + sep.length) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @primitive b.structuredFields.parseTagList
|
|
227
|
+
* @signature b.structuredFields.parseTagList(input, opts?)
|
|
228
|
+
* @since 0.15.13
|
|
229
|
+
* @status stable
|
|
230
|
+
* @related b.structuredFields.splitTopLevel
|
|
231
|
+
*
|
|
232
|
+
* Parse a NAIVE delimited `key<kvSep>value` tag list into an ordered
|
|
233
|
+
* array of `[key, value]` pairs. This is the non-quote-aware sibling
|
|
234
|
+
* of `splitTopLevel`: it is for grammars whose RFC forbids the DQUOTE
|
|
235
|
+
* structured-string form, so a bare `split` is correct and a
|
|
236
|
+
* quote-aware walk would be wrong — DKIM (RFC 6376 §3.2), DMARC
|
|
237
|
+
* (RFC 7489 §6.4), ARC (RFC 8617 §4), BIMI (RFC 9091 §4), and the
|
|
238
|
+
* MTA-STS policy grammar (RFC 8461, line/colon delimited).
|
|
239
|
+
*
|
|
240
|
+
* Pairs are returned (not a map) so a caller whose grammar permits a
|
|
241
|
+
* repeated key — MTA-STS `mx:` lines list one host each — keeps every
|
|
242
|
+
* occurrence; a caller wanting last-wins map semantics folds the
|
|
243
|
+
* pairs into a plain object itself. Order is preserved, so a caller
|
|
244
|
+
* that throws on a malformed value throws at the same point it would
|
|
245
|
+
* have mid-loop.
|
|
246
|
+
*
|
|
247
|
+
* Entries that are empty after trimming, or carry no `kvSep`, are
|
|
248
|
+
* skipped (matching every shipped parser's prior behavior).
|
|
249
|
+
*
|
|
250
|
+
* @opts
|
|
251
|
+
* sep: string|RegExp, // entry separator. default: ";" (MTA-STS uses /\r?\n/)
|
|
252
|
+
* kvSep: string, // key/value separator. default: "=" (MTA-STS uses ":")
|
|
253
|
+
* unfold: boolean, // collapse CRLF+WSP folds to a space first (DKIM FWS). default: false
|
|
254
|
+
* stripValueWs: boolean, // strip all whitespace inside each value (DKIM/ARC FWS). default: false
|
|
255
|
+
* lowerKey: boolean, // lower-case each key. default: true (set false to preserve case)
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* b.structuredFields.parseTagList("v=DKIM1; k=rsa; p=MIGf");
|
|
259
|
+
* // → [ ["v", "DKIM1"], ["k", "rsa"], ["p", "MIGf"] ]
|
|
260
|
+
*
|
|
261
|
+
* b.structuredFields.parseTagList("version:STSv1\nmx:a.example\nmx:b.example",
|
|
262
|
+
* { sep: /\r?\n/, kvSep: ":" });
|
|
263
|
+
* // → [ ["version","STSv1"], ["mx","a.example"], ["mx","b.example"] ]
|
|
264
|
+
*/
|
|
265
|
+
function parseTagList(input, opts) {
|
|
266
|
+
opts = opts || {};
|
|
267
|
+
var sep = opts.sep === undefined ? ";" : opts.sep;
|
|
268
|
+
var kvSep = opts.kvSep === undefined ? "=" : opts.kvSep;
|
|
269
|
+
var s = String(input);
|
|
270
|
+
if (opts.unfold) s = unfoldHeaderContinuations(s);
|
|
271
|
+
var kvps = parseKeyValuePieces(s.split(sep), 0, kvSep, opts.lowerKey);
|
|
272
|
+
var out = [];
|
|
273
|
+
forEachKeyValue(kvps, function (key, val) {
|
|
274
|
+
if (opts.stripValueWs) val = val.replace(/\s+/g, "");
|
|
275
|
+
out.push([key, val]);
|
|
276
|
+
});
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @primitive b.structuredFields.parseKeyValuePieces
|
|
282
|
+
* @signature b.structuredFields.parseKeyValuePieces(pieces, startIndex?, kvSep?, lowerKey?)
|
|
283
|
+
* @since 0.15.13
|
|
284
|
+
* @status stable
|
|
285
|
+
* @related b.structuredFields.parseKeyValuePiece, b.structuredFields.parseTagList
|
|
286
|
+
*
|
|
287
|
+
* Iterate an already-split list of structured-field pieces, trimming each,
|
|
288
|
+
* dropping empties, and parsing the survivors into `{ key, value }` records
|
|
289
|
+
* via `parseKeyValuePiece`. `startIndex` skips a leading non-pair token (the
|
|
290
|
+
* media type in a `Content-Type`, the brand in a `Sec-CH-UA` member); `kvSep`
|
|
291
|
+
* overrides the default `"="`; `lowerKey:false` preserves key case for a
|
|
292
|
+
* case-sensitive grammar.
|
|
293
|
+
*
|
|
294
|
+
* The split is left to the caller because the boundary discipline differs by
|
|
295
|
+
* grammar — `splitTopLevel` honours quotes/parens for RFC 8941 lists, while a
|
|
296
|
+
* plain `String(value).split(";")` is right where inner separators cannot be
|
|
297
|
+
* quoted. Per-piece dispatch (unquote, numeric coercion, poisoned-key drops)
|
|
298
|
+
* stays with the caller; this owns only the uniform iterate-trim-skip-parse
|
|
299
|
+
* spine the parsers shared verbatim.
|
|
300
|
+
*
|
|
301
|
+
* @opts
|
|
302
|
+
* startIndex: number, // default: 0 — first piece treated as a key/value pair
|
|
303
|
+
* kvSep: string, // default: "=" — key/value separator within a piece
|
|
304
|
+
* lowerKey: boolean, // default: true — lower-case each parsed key
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* var kvps = b.structuredFields.parseKeyValuePieces(
|
|
308
|
+
* b.structuredFields.splitTopLevel("max-age=600, immutable", ","));
|
|
309
|
+
* // → [ { key: "max-age", value: "600" }, { key: "immutable", value: null } ]
|
|
310
|
+
*/
|
|
311
|
+
function parseKeyValuePieces(pieces, startIndex, kvSep, lowerKey) {
|
|
312
|
+
var out = [];
|
|
313
|
+
for (var i = startIndex || 0; i < pieces.length; i += 1) {
|
|
314
|
+
var p = pieces[i].trim();
|
|
315
|
+
if (p.length === 0) continue;
|
|
316
|
+
out.push(parseKeyValuePiece(p, kvSep, lowerKey));
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @primitive b.structuredFields.forEachKeyValue
|
|
323
|
+
* @signature b.structuredFields.forEachKeyValue(kvps, handler)
|
|
324
|
+
* @since 0.15.13
|
|
325
|
+
* @status stable
|
|
326
|
+
* @related b.structuredFields.parseKeyValuePieces, b.structuredFields.parseTagList
|
|
327
|
+
*
|
|
328
|
+
* Consume the `{ key, value }` records from `parseKeyValuePieces`: skip
|
|
329
|
+
* the bare entries (those with a `null` value — a key that carried no
|
|
330
|
+
* separator), trim each surviving value, and invoke
|
|
331
|
+
* `handler(key, trimmedValue, index)`. The mirror of
|
|
332
|
+
* `parseKeyValuePieces` on the consuming side — that primitive owns the
|
|
333
|
+
* parse spine, this owns the iterate-skip-bare-trim spine every header
|
|
334
|
+
* parser repeated verbatim before dispatching.
|
|
335
|
+
*
|
|
336
|
+
* Per-key dispatch (sf-string unquoting, numeric coercion, poisoned-key
|
|
337
|
+
* drops, building a typed result) stays in the handler — a handler that
|
|
338
|
+
* `return`s skips the current entry, exactly like a `continue`. Parsers
|
|
339
|
+
* that instead treat a bare key as meaningful (a value-less directive)
|
|
340
|
+
* iterate the records directly rather than calling this.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* var b = require("blamejs");
|
|
344
|
+
*
|
|
345
|
+
* var out = {};
|
|
346
|
+
* var kvps = b.structuredFields.parseKeyValuePieces("a=1; b=2".split(";"));
|
|
347
|
+
* b.structuredFields.forEachKeyValue(kvps, function (key, value) {
|
|
348
|
+
* out[key] = value;
|
|
349
|
+
* });
|
|
350
|
+
* // out → { a: "1", b: "2" }
|
|
351
|
+
*/
|
|
352
|
+
function forEachKeyValue(kvps, handler) {
|
|
353
|
+
if (typeof handler !== "function") {
|
|
354
|
+
throw new TypeError("structuredFields.forEachKeyValue: handler must be a function");
|
|
355
|
+
}
|
|
356
|
+
for (var i = 0; i < kvps.length; i += 1) {
|
|
357
|
+
if (kvps[i].value === null) continue;
|
|
358
|
+
handler(kvps[i].key, kvps[i].value.trim(), i);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
106
362
|
/**
|
|
107
363
|
* @primitive b.structuredFields.refuseControlBytes
|
|
108
364
|
* @signature b.structuredFields.refuseControlBytes(value, opts)
|
|
@@ -155,22 +411,18 @@ function refuseControlBytes(value, opts) {
|
|
|
155
411
|
if (!opts.label || typeof opts.label !== "string") {
|
|
156
412
|
throw new TypeError("refuseControlBytes: opts.label (non-empty string) is required");
|
|
157
413
|
}
|
|
158
|
-
var allowHt = opts.allowHt !== false;
|
|
159
|
-
|
|
160
|
-
var
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// matches every framework-error class generated by `defineClass`.
|
|
169
|
-
if (opts.useNativeError === true) {
|
|
170
|
-
throw new opts.ErrorClass(msg);
|
|
171
|
-
}
|
|
172
|
-
throw new opts.ErrorClass(opts.code, msg);
|
|
414
|
+
var allowHt = opts.allowHt !== false; // ASCII HT (folding whitespace) permitted unless allowHt === false
|
|
415
|
+
if (codepointClass.firstControlCharOffset(value, { forbidTab: !allowHt }) !== -1) { // C0 + DEL codepoint range
|
|
416
|
+
var msg = opts.label + ": value contains control characters (C0 / DEL)";
|
|
417
|
+
// opts.useNativeError === true → call the ErrorClass with a
|
|
418
|
+
// single-arg `message` (matches native Error / TypeError /
|
|
419
|
+
// RangeError signatures used by defensive request-shape
|
|
420
|
+
// readers). Default false → call with (code, message) which
|
|
421
|
+
// matches every framework-error class generated by `defineClass`.
|
|
422
|
+
if (opts.useNativeError === true) {
|
|
423
|
+
throw new opts.ErrorClass(msg);
|
|
173
424
|
}
|
|
425
|
+
throw new opts.ErrorClass(opts.code, msg);
|
|
174
426
|
}
|
|
175
427
|
}
|
|
176
428
|
|
|
@@ -271,7 +523,7 @@ function containsControlBytes(value, opts) {
|
|
|
271
523
|
for (var i = 0; i < value.length; i += 1) {
|
|
272
524
|
var cc = value.charCodeAt(i);
|
|
273
525
|
if (allowHt && cc === 9) continue; // ASCII HT (folding whitespace)
|
|
274
|
-
if (cc
|
|
526
|
+
if (codepointClass.isForbiddenControlChar(cc, { forbidTab: true })) return true; // C0 + DEL codepoint range
|
|
275
527
|
}
|
|
276
528
|
return false;
|
|
277
529
|
}
|
|
@@ -707,6 +959,13 @@ function serialize(value, type, opts) {
|
|
|
707
959
|
|
|
708
960
|
module.exports = {
|
|
709
961
|
splitTopLevel: splitTopLevel,
|
|
962
|
+
splitUnquoted: splitUnquoted,
|
|
963
|
+
stripDoubleQuotes: stripDoubleQuotes,
|
|
964
|
+
parseTagList: parseTagList,
|
|
965
|
+
unfoldHeaderContinuations: unfoldHeaderContinuations,
|
|
966
|
+
parseKeyValuePiece: parseKeyValuePiece,
|
|
967
|
+
parseKeyValuePieces: parseKeyValuePieces,
|
|
968
|
+
forEachKeyValue: forEachKeyValue,
|
|
710
969
|
refuseControlBytes: refuseControlBytes,
|
|
711
970
|
containsControlBytes: containsControlBytes,
|
|
712
971
|
unquoteSfString: unquoteSfString,
|
package/lib/template.js
CHANGED
|
@@ -93,6 +93,7 @@ var nodeFs = require("node:fs");
|
|
|
93
93
|
var nodePath = require("node:path");
|
|
94
94
|
var lazyRequire = require("./lazy-require");
|
|
95
95
|
var validateOpts = require("./validate-opts");
|
|
96
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
96
97
|
|
|
97
98
|
// Lazy because b.template can be loaded before b.sandbox (which pulls
|
|
98
99
|
// in node:worker_threads). Operators not opting into sandboxed helpers
|
|
@@ -116,9 +117,6 @@ var DEFAULT_STRING_TEMPLATE_BYTES = require("./constants").BYTES.kib(256);
|
|
|
116
117
|
// HTML escape (exported)
|
|
117
118
|
// ============================================================
|
|
118
119
|
|
|
119
|
-
var ESCAPE_MAP = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
120
|
-
var ESCAPE_RE = /[&<>"']/g;
|
|
121
|
-
|
|
122
120
|
/**
|
|
123
121
|
* @primitive b.template.escapeHtml
|
|
124
122
|
* @signature b.template.escapeHtml(value)
|
|
@@ -141,8 +139,12 @@ var ESCAPE_RE = /[&<>"']/g;
|
|
|
141
139
|
*/
|
|
142
140
|
function escapeHtml(value) {
|
|
143
141
|
if (value === null || value === undefined) return "";
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
// Delegate the actual escaping to the centralized markup escaper so the
|
|
143
|
+
// five-character HTML set (& < > " ') is defined in one place — a divergence
|
|
144
|
+
// between this and the shared escaper is an XSS / XML-injection surface. The
|
|
145
|
+
// apostrophe is escaped to its numeric form for safety in single-quoted
|
|
146
|
+
// attribute contexts (the historical behavior of this primitive).
|
|
147
|
+
return markupEscape(value, { apos: "'" });
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
// ============================================================
|
package/lib/tenant-quota.js
CHANGED
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
var C = require("./constants");
|
|
54
54
|
var lazyRequire = require("./lazy-require");
|
|
55
|
+
var boundedMap = require("./bounded-map");
|
|
55
56
|
var validateOpts = require("./validate-opts");
|
|
56
57
|
var { defineClass } = require("./framework-error");
|
|
57
58
|
|
|
@@ -183,16 +184,7 @@ function create(opts) {
|
|
|
183
184
|
return defaultBytesCap;
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
if (!auditOn) return;
|
|
188
|
-
try {
|
|
189
|
-
audit().safeEmit({
|
|
190
|
-
action: action,
|
|
191
|
-
outcome: outcome,
|
|
192
|
-
metadata: metadata || {},
|
|
193
|
-
});
|
|
194
|
-
} catch (_e) { /* audit best-effort */ }
|
|
195
|
-
}
|
|
187
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
196
188
|
|
|
197
189
|
function _emitMetric(name, n) {
|
|
198
190
|
try { observability().safeEvent(name, n || 1, {}); }
|
|
@@ -363,29 +355,49 @@ function budget(opts) {
|
|
|
363
355
|
}
|
|
364
356
|
var auditOn = opts.audit !== false;
|
|
365
357
|
|
|
366
|
-
//
|
|
358
|
+
// TRUE sliding window (RFC-ish rolling counter, as advertised) — the prior
|
|
359
|
+
// first-call-pinned fixed window admitted ~2x the cap in a boundary burst.
|
|
360
|
+
// The window is split into BINS sub-bins; each observe lands in the current
|
|
361
|
+
// bin and the cap is enforced against the trailing-window SUM, so a burst
|
|
362
|
+
// straddling the reset can't double the rate. Mirrors network-byte-quota's
|
|
363
|
+
// _slideAndSum ring (here with two counters + a configurable bin width).
|
|
364
|
+
var BINS = 12;
|
|
365
|
+
var binMs = Math.max(1, Math.floor(windowMs / BINS));
|
|
366
|
+
|
|
367
|
+
// tenantId → { calls: number[BINS], rows: number[BINS], startBin }
|
|
367
368
|
var counters = new Map();
|
|
368
369
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
// Advance the ring so its BINS bins cover the trailing [nowBin-BINS+1 .. nowBin],
|
|
371
|
+
// zeroing bins that scrolled out of the window. Returns the tenant's ring.
|
|
372
|
+
function _slide(tenantId, now) {
|
|
373
|
+
var nowBin = Math.floor(now / binMs);
|
|
374
|
+
// A freshly-created ring is anchored at the current bin, so the advance
|
|
375
|
+
// below evaluates to 0 (no-op) on the create path — get-or-insert and the
|
|
376
|
+
// slide stay one code path.
|
|
377
|
+
var c = boundedMap.getOrInsert(counters, tenantId, function () {
|
|
378
|
+
return { calls: new Array(BINS).fill(0), rows: new Array(BINS).fill(0), startBin: nowBin - (BINS - 1) };
|
|
379
|
+
});
|
|
380
|
+
var advance = nowBin - (c.startBin + (BINS - 1));
|
|
381
|
+
if (advance > 0) {
|
|
382
|
+
if (advance >= BINS) {
|
|
383
|
+
c.calls.fill(0); c.rows.fill(0);
|
|
384
|
+
} else {
|
|
385
|
+
for (var i = 0; i < BINS - advance; i++) { c.calls[i] = c.calls[i + advance]; c.rows[i] = c.rows[i + advance]; }
|
|
386
|
+
for (var k = BINS - advance; k < BINS; k++) { c.calls[k] = 0; c.rows[k] = 0; }
|
|
387
|
+
}
|
|
388
|
+
c.startBin = nowBin - (BINS - 1);
|
|
374
389
|
}
|
|
375
390
|
return c;
|
|
376
391
|
}
|
|
377
392
|
|
|
378
|
-
function
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
action: action,
|
|
383
|
-
outcome: outcome,
|
|
384
|
-
metadata: metadata || {},
|
|
385
|
-
});
|
|
386
|
-
} catch (_e) { /* audit best-effort */ }
|
|
393
|
+
function _sum(c) {
|
|
394
|
+
var calls = 0, rows = 0;
|
|
395
|
+
for (var i = 0; i < BINS; i++) { calls += c.calls[i]; rows += c.rows[i]; }
|
|
396
|
+
return { calls: calls, rowsRead: rows };
|
|
387
397
|
}
|
|
388
398
|
|
|
399
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
400
|
+
|
|
389
401
|
function _emitMetric(name, n) {
|
|
390
402
|
try { observability().safeEvent(name, n || 1, {}); }
|
|
391
403
|
catch (_e) { /* drop-silent */ }
|
|
@@ -396,16 +408,19 @@ function budget(opts) {
|
|
|
396
408
|
"tenantQuota.budget.observe: tenantId", TenantQuotaError, "tenant-quota/bad-tenant");
|
|
397
409
|
info = info || {};
|
|
398
410
|
var rowsRead = (typeof info.rowsRead === "number" && info.rowsRead >= 0) ? info.rowsRead : 0;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
c
|
|
411
|
+
// info.now lets a caller / test supply a deterministic clock (idiomatic —
|
|
412
|
+
// mirrors auth verifiers' opts.now); defaults to wall-clock.
|
|
413
|
+
var now = (typeof info.now === "number") ? info.now : Date.now();
|
|
414
|
+
var c = _slide(tenantId, now);
|
|
415
|
+
c.calls[BINS - 1] += 1;
|
|
416
|
+
c.rows[BINS - 1] += rowsRead;
|
|
417
|
+
var tot = _sum(c);
|
|
403
418
|
var maxCalls = Math.max(1, Math.floor(qpsCap * (windowMs / C.TIME.seconds(1))));
|
|
404
|
-
if (
|
|
419
|
+
if (tot.calls > maxCalls || tot.rowsRead > rowsCap) {
|
|
405
420
|
_emitAudit("tenant.budget.exceeded", "denied", {
|
|
406
421
|
tenantId: tenantId,
|
|
407
|
-
calls:
|
|
408
|
-
rowsRead:
|
|
422
|
+
calls: tot.calls,
|
|
423
|
+
rowsRead: tot.rowsRead,
|
|
409
424
|
qpsCap: qpsCap,
|
|
410
425
|
rowsCap: rowsCap,
|
|
411
426
|
windowMs: windowMs,
|
|
@@ -413,19 +428,19 @@ function budget(opts) {
|
|
|
413
428
|
_emitMetric("tenant.budget.exceeded", 1);
|
|
414
429
|
throw new TenantQuotaError("tenant-quota/budget-exceeded",
|
|
415
430
|
"tenantQuota.budget: tenant '" + tenantId + "' exceeded budget " +
|
|
416
|
-
"(calls=" +
|
|
431
|
+
"(calls=" + tot.calls + "/" + maxCalls + ", rowsRead=" + tot.rowsRead +
|
|
417
432
|
"/" + rowsCap + ", windowMs=" + windowMs + ")");
|
|
418
433
|
}
|
|
419
|
-
return { calls:
|
|
434
|
+
return { calls: tot.calls, rowsRead: tot.rowsRead, windowMs: windowMs };
|
|
420
435
|
}
|
|
421
436
|
|
|
422
437
|
function snapshot(tenantId) {
|
|
423
|
-
|
|
424
|
-
var c = counters.get(tenantId);
|
|
425
|
-
if (!c || (now - c.windowStart) >= windowMs) {
|
|
438
|
+
if (!counters.has(tenantId)) {
|
|
426
439
|
return { tenantId: tenantId, calls: 0, rowsRead: 0, windowMs: windowMs };
|
|
427
440
|
}
|
|
428
|
-
|
|
441
|
+
var c = _slide(tenantId, Date.now());
|
|
442
|
+
var tot = _sum(c);
|
|
443
|
+
return { tenantId: tenantId, calls: tot.calls, rowsRead: tot.rowsRead, windowMs: windowMs };
|
|
429
444
|
}
|
|
430
445
|
|
|
431
446
|
function reset(tenantId) {
|
package/lib/tsa.js
CHANGED
|
@@ -53,8 +53,10 @@
|
|
|
53
53
|
|
|
54
54
|
var nodeCrypto = require("node:crypto");
|
|
55
55
|
var bCrypto = require("./crypto");
|
|
56
|
+
var safeBuffer = require("./safe-buffer");
|
|
56
57
|
var asn1 = require("./asn1-der");
|
|
57
58
|
var cms = require("./cms-codec");
|
|
59
|
+
var x509Chain = require("./x509-chain");
|
|
58
60
|
var validateOpts = require("./validate-opts");
|
|
59
61
|
var { defineClass } = require("./framework-error");
|
|
60
62
|
|
|
@@ -104,12 +106,12 @@ var DIGEST_OID_TO_NODE = {
|
|
|
104
106
|
"2.16.840.1.101.3.4.2.10": "sha3-512",
|
|
105
107
|
};
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
109
|
+
var _bytes = safeBuffer.makeByteCoercer({
|
|
110
|
+
errorClass: TsaError,
|
|
111
|
+
typeCode: "tsa/bad-bytes",
|
|
112
|
+
messagePrefix: "tsa: ",
|
|
113
|
+
messageSuffix: " must be Buffer / Uint8Array / string",
|
|
114
|
+
});
|
|
113
115
|
|
|
114
116
|
function _normHex(h) {
|
|
115
117
|
return String(h).replace(/[^0-9a-fA-F]/g, "").replace(/^0+(?=.)/, "").toUpperCase();
|
|
@@ -538,8 +540,10 @@ function _verifyChain(signerCertDer, tokenCerts, trustAnchorsPem, at) {
|
|
|
538
540
|
throw new TsaError("tsa/chain-loop", "tsa.verifyToken: certificate chain did not terminate");
|
|
539
541
|
}
|
|
540
542
|
function _issued(issuer, subject) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
+
// Enforces basicConstraints cA:TRUE on the issuer in addition to the
|
|
544
|
+
// checkIssued + signature linkage — a non-CA cert can never be a chain
|
|
545
|
+
// issuer (basicConstraints bypass, CVE-2002-0862 class).
|
|
546
|
+
return x509Chain.issuerValidlyIssued(issuer, subject);
|
|
543
547
|
}
|
|
544
548
|
function _assertValidAt(cert, atMs) {
|
|
545
549
|
if (atMs < cert.validFromDate.getTime() || atMs > cert.validToDate.getTime()) {
|
|
@@ -658,15 +662,10 @@ function verifyToken(token, opts) {
|
|
|
658
662
|
throw new TsaError("tsa/bad-trust-anchors",
|
|
659
663
|
"tsa.verifyToken: trustAnchorsPem must be a non-empty PEM string or array of PEM strings");
|
|
660
664
|
}
|
|
661
|
-
// A supplied opts.at must be a valid Date — an Invalid Date would
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
|
|
666
|
-
throw new TsaError("tsa/bad-at", "tsa.verifyToken: opts.at must be a valid Date");
|
|
667
|
-
}
|
|
668
|
-
at = opts.at;
|
|
669
|
-
}
|
|
665
|
+
// A supplied opts.at must be a valid Date — an Invalid Date would make
|
|
666
|
+
// every validity-window comparison NaN (silently disabling it).
|
|
667
|
+
validateOpts.optionalDate(opts.at, "tsa.verifyToken: opts.at", TsaError, "tsa/bad-at");
|
|
668
|
+
var at = (opts.at !== undefined && opts.at !== null) ? opts.at : tst.genTime;
|
|
670
669
|
_verifyChain(signerCertDer, sd.certificates, anchors, at);
|
|
671
670
|
}
|
|
672
671
|
|