@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/http-client.js
CHANGED
|
@@ -53,6 +53,7 @@ var safeBuffer = require("./safe-buffer");
|
|
|
53
53
|
var safeUrl = require("./safe-url");
|
|
54
54
|
var ssrfGuard = require("./ssrf-guard");
|
|
55
55
|
var networkProxy = require("./network-proxy");
|
|
56
|
+
var numericBounds = require("./numeric-bounds");
|
|
56
57
|
var validateOpts = require("./validate-opts");
|
|
57
58
|
var { FrameworkError, HttpClientError } = require("./framework-error");
|
|
58
59
|
|
|
@@ -165,15 +166,9 @@ function configurePool(opts) {
|
|
|
165
166
|
"'. Allowed: " + allowed.join(", "));
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
" must be a positive integer, got " + JSON.stringify(value));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (opts.maxSockets !== undefined) _requirePositiveInt("maxSockets", opts.maxSockets);
|
|
175
|
-
if (opts.maxFreeSockets !== undefined) _requirePositiveInt("maxFreeSockets", opts.maxFreeSockets);
|
|
176
|
-
if (opts.keepAliveMsecs !== undefined) _requirePositiveInt("keepAliveMsecs", opts.keepAliveMsecs);
|
|
169
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
170
|
+
["maxSockets", "maxFreeSockets", "keepAliveMsecs"], "httpClient.configurePool",
|
|
171
|
+
HttpClientError, "httpclient/bad-opts", { permanent: true });
|
|
177
172
|
if (opts.keepAlive !== undefined && typeof opts.keepAlive !== "boolean") {
|
|
178
173
|
throw new Error("httpClient.configurePool: keepAlive must be a boolean");
|
|
179
174
|
}
|
|
@@ -721,6 +716,9 @@ function _stripCrossOriginAuth(headers) {
|
|
|
721
716
|
* responseMode: "buffer", // "buffer" | "stream" | "always-resolve"
|
|
722
717
|
* maxResponseBytes: undefined, // 16 MiB control / 1 GiB GET defaults; ignored in "stream"
|
|
723
718
|
* onChunk: undefined, // (chunk: Buffer) => void — fires per response chunk
|
|
719
|
+
* maxBytesPerSec: undefined, // token-bucket bandwidth cap (bytes/sec) — paces BOTH the download response and the upload body with backpressure
|
|
720
|
+
* downloadTransform: undefined, // Transform | () => Transform | array — interpose on the response stream (e.g. a hashing or progress Transform)
|
|
721
|
+
* uploadTransform: undefined, // Transform | () => Transform | array — interpose on the request body before the wire
|
|
724
722
|
* signal: undefined, // AbortSignal — propagated to req / stream
|
|
725
723
|
* errorClass: HttpClientError, // FrameworkError subclass for thrown errors
|
|
726
724
|
* observer: undefined, // (stage, info) => void — lifecycle hook
|
|
@@ -738,6 +736,132 @@ function _stripCrossOriginAuth(headers) {
|
|
|
738
736
|
* });
|
|
739
737
|
* // → { statusCode: 200, headers: { "content-type": "application/json", ... }, body: <Buffer> }
|
|
740
738
|
*/
|
|
739
|
+
// Token-bucket bandwidth throttle as a Transform. Splits each chunk to the
|
|
740
|
+
// credit available, releasing at most `bytesPerSec` per second (1s burst
|
|
741
|
+
// capacity). Because `_transform`'s callback is not called until the whole
|
|
742
|
+
// chunk has been released, backpressure propagates upstream — on a download
|
|
743
|
+
// the socket pauses, on an upload the body source pauses. Wired into both the
|
|
744
|
+
// response (download) and request-body (upload) pipelines so the helpers'
|
|
745
|
+
// SSRF guard / DNS pinning / byte caps / atomic-rename are preserved.
|
|
746
|
+
function _throttleStream(bytesPerSec) {
|
|
747
|
+
var capacity = bytesPerSec; // allow at most 1s worth of burst
|
|
748
|
+
var tokens = capacity;
|
|
749
|
+
var last = Date.now();
|
|
750
|
+
function refill() {
|
|
751
|
+
var now = Date.now();
|
|
752
|
+
if (now > last) {
|
|
753
|
+
tokens = Math.min(capacity, tokens + ((now - last) / 1000) * bytesPerSec);
|
|
754
|
+
last = now;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return new nodeStream.Transform({
|
|
758
|
+
transform: function (chunk, _enc, cb) {
|
|
759
|
+
var self = this;
|
|
760
|
+
var offset = 0;
|
|
761
|
+
function pump() {
|
|
762
|
+
refill();
|
|
763
|
+
if (offset >= chunk.length) { cb(); return; }
|
|
764
|
+
var avail = Math.floor(tokens);
|
|
765
|
+
if (avail >= 1) {
|
|
766
|
+
var n = Math.min(chunk.length - offset, avail);
|
|
767
|
+
tokens -= n;
|
|
768
|
+
self.push(chunk.subarray(offset, offset + n));
|
|
769
|
+
offset += n;
|
|
770
|
+
if (offset >= chunk.length) { cb(); return; }
|
|
771
|
+
setImmediate(pump); // more bytes, maybe more credit already
|
|
772
|
+
} else {
|
|
773
|
+
// ms to accrue one more byte of credit = (deficit / rate) seconds.
|
|
774
|
+
var waitMs = Math.ceil(C.TIME.seconds((1 - tokens) / bytesPerSec)) + 1;
|
|
775
|
+
var t = setTimeout(pump, waitMs);
|
|
776
|
+
if (t && typeof t.unref === "function") t.unref();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
pump();
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Validate + normalize a transform opt into an array. Each entry may be a
|
|
785
|
+
// Transform stream instance OR a `() => Transform` factory (the factory form is
|
|
786
|
+
// retry/redirect-safe — a single Transform instance can only be piped once, so
|
|
787
|
+
// a fresh one is built per attempt). Returns { ok: [...] } or { err: Error }.
|
|
788
|
+
function _coerceTransforms(value, errorClass, name) {
|
|
789
|
+
if (value === undefined || value === null) return { ok: [] };
|
|
790
|
+
var list = Array.isArray(value) ? value : [value];
|
|
791
|
+
for (var i = 0; i < list.length; i++) {
|
|
792
|
+
var t = list[i];
|
|
793
|
+
var isFactory = typeof t === "function";
|
|
794
|
+
var isTransform = t && typeof t.pipe === "function" && typeof t.write === "function";
|
|
795
|
+
if (!isFactory && !isTransform) {
|
|
796
|
+
return { err: _makeError(errorClass, "BAD_ARG",
|
|
797
|
+
name + " must be a Transform stream or a () => Transform factory (or an array of them)", true) };
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return { ok: list };
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Resolve a transform entry to a fresh stream instance (call the factory form).
|
|
804
|
+
function _resolveStage(t) { return typeof t === "function" ? t() : t; }
|
|
805
|
+
|
|
806
|
+
// Compose the response (download) stream: source → [throttle?] → [transforms…]
|
|
807
|
+
// → observe(progress + onChunk). Every stage is a Transform so backpressure
|
|
808
|
+
// flows from the consumer back to the socket (the throttle paces it). Returns
|
|
809
|
+
// `source` unchanged when nothing is interposed (preserves the bare-stream
|
|
810
|
+
// contract). A stage error propagates downstream so the returned body emits it.
|
|
811
|
+
function _buildDownloadStream(source, emitDownload, onChunk, throttleBps, transforms) {
|
|
812
|
+
var stages = [];
|
|
813
|
+
if (throttleBps) stages.push(_throttleStream(throttleBps));
|
|
814
|
+
for (var i = 0; i < transforms.length; i++) stages.push(_resolveStage(transforms[i]));
|
|
815
|
+
if (stages.length === 0 && !emitDownload && !onChunk) return source;
|
|
816
|
+
var observe = new nodeStream.Transform({
|
|
817
|
+
transform: function (chunk, _enc, cb) {
|
|
818
|
+
if (emitDownload) emitDownload(chunk.length);
|
|
819
|
+
if (onChunk) { try { onChunk(chunk); } catch (_e) { /* operator hook — drop-silent */ } }
|
|
820
|
+
cb(null, chunk);
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
var chain = [source].concat(stages).concat([observe]);
|
|
824
|
+
for (var c = 0; c < chain.length - 1; c++) {
|
|
825
|
+
var src = chain[c], dst = chain[c + 1];
|
|
826
|
+
src.on("error", (function (d) { return function (e) { d.destroy(e); }; })(dst));
|
|
827
|
+
src.pipe(dst);
|
|
828
|
+
}
|
|
829
|
+
return observe;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Build the upload throttle/transform stages (parallel to the download side).
|
|
833
|
+
function _uploadStages(throttleBps, transforms) {
|
|
834
|
+
var stages = [];
|
|
835
|
+
if (throttleBps) stages.push(_throttleStream(throttleBps));
|
|
836
|
+
for (var i = 0; i < transforms.length; i++) stages.push(_resolveStage(transforms[i]));
|
|
837
|
+
return stages;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Pipe a body (stream | Buffer | string) through throttle/transform stages into
|
|
841
|
+
// the request sink, pacing the upload and emitting progress on bytes delivered
|
|
842
|
+
// to the sink. Used only when a throttle/transform is configured; the plain
|
|
843
|
+
// no-stages path keeps its original direct write. A stage/source error tears
|
|
844
|
+
// down the request via onBodyError.
|
|
845
|
+
function _pipeThrottledUpload(body, sink, stages, emitUpload, onBodyError) {
|
|
846
|
+
var source = (body && typeof body.pipe === "function")
|
|
847
|
+
? body
|
|
848
|
+
: nodeStream.Readable.from(Buffer.isBuffer(body) ? body : Buffer.from(String(body), "utf8"));
|
|
849
|
+
var observe = new nodeStream.Transform({
|
|
850
|
+
transform: function (chunk, _enc, cb) {
|
|
851
|
+
if (emitUpload) emitUpload(chunk.length);
|
|
852
|
+
cb(null, chunk);
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
var chain = [source].concat(stages).concat([observe]);
|
|
856
|
+
for (var c = 0; c < chain.length - 1; c++) {
|
|
857
|
+
var s = chain[c], d = chain[c + 1];
|
|
858
|
+
s.on("error", (function (dd) { return function (e) { dd.destroy(e); }; })(d));
|
|
859
|
+
s.pipe(d);
|
|
860
|
+
}
|
|
861
|
+
observe.on("error", onBodyError);
|
|
862
|
+
observe.pipe(sink);
|
|
863
|
+
}
|
|
864
|
+
|
|
741
865
|
function request(opts) {
|
|
742
866
|
if (!opts || !opts.url) {
|
|
743
867
|
return Promise.reject(_makeError(opts && opts.errorClass, "BAD_ARG", "url is required", true));
|
|
@@ -770,6 +894,20 @@ function request(opts) {
|
|
|
770
894
|
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
771
895
|
"onChunk must be a function (chunk: Buffer) -> void", true));
|
|
772
896
|
}
|
|
897
|
+
// Bandwidth cap + transform interpose (download response + upload request
|
|
898
|
+
// body) — validated here, derived per-attempt in _requestSingle. maxBytesPerSec
|
|
899
|
+
// paces BOTH legs; downloadTransform / uploadTransform interpose custom
|
|
900
|
+
// Transform(s) (instance or () => Transform factory) on the respective leg.
|
|
901
|
+
if (opts.maxBytesPerSec !== undefined && opts.maxBytesPerSec !== null) {
|
|
902
|
+
if (typeof opts.maxBytesPerSec !== "number" || !isFinite(opts.maxBytesPerSec) || opts.maxBytesPerSec <= 0) {
|
|
903
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
904
|
+
"maxBytesPerSec must be a positive finite number (bytes/sec)", true));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
var _dlChk = _coerceTransforms(opts.downloadTransform, opts.errorClass, "downloadTransform");
|
|
908
|
+
if (_dlChk.err) return Promise.reject(_dlChk.err);
|
|
909
|
+
var _ulChk = _coerceTransforms(opts.uploadTransform, opts.errorClass, "uploadTransform");
|
|
910
|
+
if (_ulChk.err) return Promise.reject(_ulChk.err);
|
|
773
911
|
if (opts.jar !== undefined && opts.jar !== null) {
|
|
774
912
|
if (typeof opts.jar !== "object" ||
|
|
775
913
|
typeof opts.jar.cookieHeaderFor !== "function" ||
|
|
@@ -1384,6 +1522,12 @@ function _requestSingle(opts) {
|
|
|
1384
1522
|
// ---- _requestH1: existing node:http(s) path ----
|
|
1385
1523
|
|
|
1386
1524
|
function _requestH1(transport, u, opts) {
|
|
1525
|
+
// Bandwidth/transform interpose — opts validated in request(); derived fresh
|
|
1526
|
+
// per attempt (each builds its own throttle + resolves transform factories, so
|
|
1527
|
+
// a retry / redirect never reuses a consumed Transform).
|
|
1528
|
+
var throttleBps = opts.maxBytesPerSec || null; // validated in request(); positive number or absent
|
|
1529
|
+
var downloadTransforms = _coerceTransforms(opts.downloadTransform, opts.errorClass, "downloadTransform").ok;
|
|
1530
|
+
var uploadTransforms = _coerceTransforms(opts.uploadTransform, opts.errorClass, "uploadTransform").ok;
|
|
1387
1531
|
return new Promise(function (resolve, reject) {
|
|
1388
1532
|
var method = (opts.method || "GET").toUpperCase();
|
|
1389
1533
|
var headers = Object.assign({}, opts.headers || {});
|
|
@@ -1402,7 +1546,12 @@ function _requestH1(transport, u, opts) {
|
|
|
1402
1546
|
return;
|
|
1403
1547
|
}
|
|
1404
1548
|
|
|
1405
|
-
if (Buffer.isBuffer(opts.body)) {
|
|
1549
|
+
if (Buffer.isBuffer(opts.body) && uploadTransforms.length === 0) {
|
|
1550
|
+
// A size-changing uploadTransform (gzip / encrypt / frame) makes the
|
|
1551
|
+
// original buffer length wrong, so the server would truncate extra bytes
|
|
1552
|
+
// or wait for bytes that never arrive. Omit Content-Length in that case
|
|
1553
|
+
// and let the body be chunked, framed on the actual transformed bytes. A
|
|
1554
|
+
// throttle alone preserves size, so Content-Length stays valid then.
|
|
1406
1555
|
headers["Content-Length"] = opts.body.length;
|
|
1407
1556
|
}
|
|
1408
1557
|
// CVE-2026-22036 mitigation — refuse compressed responses by
|
|
@@ -1470,35 +1619,30 @@ function _requestH1(transport, u, opts) {
|
|
|
1470
1619
|
_rejectStreamHttpError(res, opts.errorClass, res.statusCode, res.statusMessage || "", _reject);
|
|
1471
1620
|
return;
|
|
1472
1621
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
// surfaces the error through their own pipe handler.
|
|
1482
|
-
var passthrough = new nodeStream.PassThrough();
|
|
1483
|
-
res.on("data", function (chunk) {
|
|
1484
|
-
_emitDownload(chunk.length);
|
|
1485
|
-
if (onChunk) {
|
|
1486
|
-
try { onChunk(chunk); }
|
|
1487
|
-
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1488
|
-
}
|
|
1489
|
-
passthrough.write(chunk);
|
|
1490
|
-
});
|
|
1491
|
-
res.on("end", function () { passthrough.end(); });
|
|
1492
|
-
res.on("error", function (e) { passthrough.destroy(e); });
|
|
1493
|
-
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: passthrough });
|
|
1494
|
-
}
|
|
1495
|
-
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: res });
|
|
1622
|
+
// Compose the response stream with the optional bandwidth throttle +
|
|
1623
|
+
// custom transforms + progress/onChunk observation. Each is a Transform
|
|
1624
|
+
// so backpressure reaches the socket; onChunk sees the bytes in order
|
|
1625
|
+
// (for hash-as-you-go) and a throw from it is dropped. Returns `res`
|
|
1626
|
+
// unchanged when nothing is interposed (bare-stream contract).
|
|
1627
|
+
var body = _buildDownloadStream(
|
|
1628
|
+
res, onDownloadProgress ? _emitDownload : null, onChunk, throttleBps, downloadTransforms);
|
|
1629
|
+
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: body });
|
|
1496
1630
|
}
|
|
1497
1631
|
|
|
1498
1632
|
var collector = safeBuffer.boundedChunkCollector({ maxBytes: maxResponseBytes });
|
|
1499
1633
|
var capExceeded = false;
|
|
1500
1634
|
|
|
1501
|
-
|
|
1635
|
+
// Route the buffered read through the SAME throttle + transform pipeline
|
|
1636
|
+
// as stream mode, so maxBytesPerSec and downloadTransform apply in buffer
|
|
1637
|
+
// and always-resolve modes too (both documented to pace / transform the
|
|
1638
|
+
// download regardless of how the body is consumed). With nothing
|
|
1639
|
+
// interposed this returns `res` unchanged, so the plain path is identical.
|
|
1640
|
+
// The cap now bounds post-transform bytes — strictly safer for a
|
|
1641
|
+
// decompressing transform. Progress + onChunk fire inside the pipeline.
|
|
1642
|
+
var dlSource = _buildDownloadStream(
|
|
1643
|
+
res, onDownloadProgress ? _emitDownload : null, onChunk, throttleBps, downloadTransforms);
|
|
1644
|
+
|
|
1645
|
+
dlSource.on("data", function (chunk) {
|
|
1502
1646
|
if (capExceeded) return;
|
|
1503
1647
|
try { collector.push(chunk); }
|
|
1504
1648
|
catch (_e) {
|
|
@@ -1508,13 +1652,8 @@ function _requestH1(transport, u, opts) {
|
|
|
1508
1652
|
"response body exceeds " + maxResponseBytes + " bytes", true));
|
|
1509
1653
|
return;
|
|
1510
1654
|
}
|
|
1511
|
-
_emitDownload(chunk.length);
|
|
1512
|
-
if (onChunk) {
|
|
1513
|
-
try { onChunk(chunk); }
|
|
1514
|
-
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1515
|
-
}
|
|
1516
1655
|
});
|
|
1517
|
-
|
|
1656
|
+
dlSource.on("end", function () {
|
|
1518
1657
|
if (capExceeded) return;
|
|
1519
1658
|
var buf = collector.result();
|
|
1520
1659
|
if (observer) observer("response:end", {
|
|
@@ -1544,7 +1683,7 @@ function _requestH1(transport, u, opts) {
|
|
|
1544
1683
|
_isPermanentStatus(res.statusCode), res.statusCode));
|
|
1545
1684
|
}
|
|
1546
1685
|
});
|
|
1547
|
-
|
|
1686
|
+
dlSource.on("error", function (e) {
|
|
1548
1687
|
if (capExceeded) return;
|
|
1549
1688
|
if (observer) observer("error", { phase: "response", message: e.message });
|
|
1550
1689
|
_reject(_makeError(opts.errorClass, e.code || "RES_ERROR", e.message, false));
|
|
@@ -1588,7 +1727,17 @@ function _requestH1(transport, u, opts) {
|
|
|
1588
1727
|
catch (_e) { /* progress hooks are best-effort */ }
|
|
1589
1728
|
}
|
|
1590
1729
|
|
|
1591
|
-
|
|
1730
|
+
var ulStages = _uploadStages(throttleBps, uploadTransforms);
|
|
1731
|
+
if (opts.body != null && opts.body !== "" && ulStages.length > 0) {
|
|
1732
|
+
// Pace / transform the upload body through the stages, into req.
|
|
1733
|
+
_pipeThrottledUpload(opts.body, req, ulStages,
|
|
1734
|
+
onUploadProgress ? _emitUpload : null,
|
|
1735
|
+
function (e) {
|
|
1736
|
+
try { req.destroy(); } catch (_) { /* best-effort req teardown */ }
|
|
1737
|
+
_reject(_makeError(opts.errorClass, "REQ_BODY_ERROR",
|
|
1738
|
+
"request body stream error: " + e.message, false));
|
|
1739
|
+
});
|
|
1740
|
+
} else if (opts.body && typeof opts.body.pipe === "function") {
|
|
1592
1741
|
if (onUploadProgress) {
|
|
1593
1742
|
opts.body.on("data", function (c) { _emitUpload(c.length); });
|
|
1594
1743
|
}
|
|
@@ -1623,6 +1772,10 @@ function _requestH1(transport, u, opts) {
|
|
|
1623
1772
|
// ---- _requestH2: node:http2 path ----
|
|
1624
1773
|
|
|
1625
1774
|
function _requestH2(transport, u, opts) {
|
|
1775
|
+
// Bandwidth/transform interpose — see _requestH1; derived fresh per attempt.
|
|
1776
|
+
var throttleBps = opts.maxBytesPerSec || null; // validated in request(); positive number or absent
|
|
1777
|
+
var downloadTransforms = _coerceTransforms(opts.downloadTransform, opts.errorClass, "downloadTransform").ok;
|
|
1778
|
+
var uploadTransforms = _coerceTransforms(opts.uploadTransform, opts.errorClass, "uploadTransform").ok;
|
|
1626
1779
|
return new Promise(function (resolve, reject) {
|
|
1627
1780
|
var method = (opts.method || "GET").toUpperCase();
|
|
1628
1781
|
var responseMode = opts.responseMode || "buffer";
|
|
@@ -1642,7 +1795,13 @@ function _requestH2(transport, u, opts) {
|
|
|
1642
1795
|
}
|
|
1643
1796
|
|
|
1644
1797
|
var headers = _toH2Headers(method, u, opts.headers || {});
|
|
1645
|
-
|
|
1798
|
+
// Omit content-length when a size-changing uploadTransform is interposed —
|
|
1799
|
+
// the original buffer length no longer frames the transformed body (HTTP/2
|
|
1800
|
+
// frames via DATA + END_STREAM, so no header is required). A throttle alone
|
|
1801
|
+
// keeps the size, so the length stays valid then.
|
|
1802
|
+
if (Buffer.isBuffer(opts.body) && uploadTransforms.length === 0) {
|
|
1803
|
+
headers["content-length"] = String(opts.body.length);
|
|
1804
|
+
}
|
|
1646
1805
|
|
|
1647
1806
|
if (observer) observer("request:start", { method: method, url: String(opts.url), protocol: "h2" });
|
|
1648
1807
|
|
|
@@ -1686,24 +1845,29 @@ function _requestH2(transport, u, opts) {
|
|
|
1686
1845
|
_rejectStreamHttpError(stream, opts.errorClass, statusCode, "", _reject);
|
|
1687
1846
|
return;
|
|
1688
1847
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
stream.on("data", function (chunk) {
|
|
1692
|
-
try { onChunkH2(chunk); }
|
|
1693
|
-
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1694
|
-
passthroughH2.write(chunk);
|
|
1695
|
-
});
|
|
1696
|
-
stream.on("end", function () { passthroughH2.end(); });
|
|
1697
|
-
stream.on("error", function (e) { passthroughH2.destroy(e); });
|
|
1698
|
-
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: passthroughH2 });
|
|
1699
|
-
}
|
|
1700
|
-
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: stream });
|
|
1848
|
+
var bodyH2 = _buildDownloadStream(stream, null, onChunkH2, throttleBps, downloadTransforms);
|
|
1849
|
+
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: bodyH2 });
|
|
1701
1850
|
}
|
|
1702
1851
|
|
|
1703
1852
|
var collector = safeBuffer.boundedChunkCollector({ maxBytes: maxResponseBytes });
|
|
1704
1853
|
var capExceeded = false;
|
|
1705
1854
|
|
|
1706
|
-
|
|
1855
|
+
// Apply the throttle + transform pipeline in buffer / always-resolve mode
|
|
1856
|
+
// too (matches H1 + the documented contract); returns `stream` unchanged
|
|
1857
|
+
// when nothing is interposed. onChunk fires inside the pipeline.
|
|
1858
|
+
var dlH2 = _buildDownloadStream(stream, null, onChunkH2, throttleBps, downloadTransforms);
|
|
1859
|
+
// When a stage is interposed, a transform error surfaces on the pipeline
|
|
1860
|
+
// tail, not the raw h2 stream — reject on it. The plain path (dlH2 ===
|
|
1861
|
+
// stream) is already covered by the stream-level error handler below.
|
|
1862
|
+
if (dlH2 !== stream) {
|
|
1863
|
+
dlH2.on("error", function (e) {
|
|
1864
|
+
if (capExceeded) return;
|
|
1865
|
+
if (observer) observer("error", { phase: "stream", message: e.message });
|
|
1866
|
+
_reject(_makeError(opts.errorClass, e.code || "H2_STREAM_ERROR", e.message, false));
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
dlH2.on("data", function (chunk) {
|
|
1707
1871
|
if (capExceeded) return;
|
|
1708
1872
|
try { collector.push(chunk); }
|
|
1709
1873
|
catch (_e) {
|
|
@@ -1713,12 +1877,8 @@ function _requestH2(transport, u, opts) {
|
|
|
1713
1877
|
"response body exceeds " + maxResponseBytes + " bytes", true));
|
|
1714
1878
|
return;
|
|
1715
1879
|
}
|
|
1716
|
-
if (onChunkH2) {
|
|
1717
|
-
try { onChunkH2(chunk); }
|
|
1718
|
-
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1719
|
-
}
|
|
1720
1880
|
});
|
|
1721
|
-
|
|
1881
|
+
dlH2.on("end", function () {
|
|
1722
1882
|
if (capExceeded) return;
|
|
1723
1883
|
var buf = collector.result();
|
|
1724
1884
|
if (observer) observer("response:end", {
|
|
@@ -1756,7 +1916,14 @@ function _requestH2(transport, u, opts) {
|
|
|
1756
1916
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1757
1917
|
}
|
|
1758
1918
|
|
|
1759
|
-
|
|
1919
|
+
var ulStagesH2 = _uploadStages(throttleBps, uploadTransforms);
|
|
1920
|
+
if (opts.body != null && opts.body !== "" && ulStagesH2.length > 0) {
|
|
1921
|
+
_pipeThrottledUpload(opts.body, stream, ulStagesH2, null, function (e) {
|
|
1922
|
+
try { stream.close(http2.constants.NGHTTP2_INTERNAL_ERROR); } catch (_) { /* best-effort h2 stream cancel */ }
|
|
1923
|
+
_reject(_makeError(opts.errorClass, "REQ_BODY_ERROR",
|
|
1924
|
+
"request body stream error: " + e.message, false));
|
|
1925
|
+
});
|
|
1926
|
+
} else if (opts.body && typeof opts.body.pipe === "function") {
|
|
1760
1927
|
opts.body.on("error", function (e) {
|
|
1761
1928
|
try { stream.close(http2.constants.NGHTTP2_INTERNAL_ERROR); } catch (_) { /* best-effort h2 stream cancel */ }
|
|
1762
1929
|
_reject(_makeError(opts.errorClass, "REQ_BODY_ERROR",
|
|
@@ -1825,12 +1992,8 @@ function _validateDownloadOpts(opts) {
|
|
|
1825
1992
|
}
|
|
1826
1993
|
validateOpts.optionalPositiveFinite(opts.timeoutMs, "downloadStream: timeoutMs",
|
|
1827
1994
|
HttpClientError, "httpclient/bad-opts");
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
Math.floor(opts.maxBytes) !== opts.maxBytes)) {
|
|
1831
|
-
throw _hcErr("httpclient/bad-opts",
|
|
1832
|
-
"downloadStream: maxBytes must be a positive finite integer");
|
|
1833
|
-
}
|
|
1995
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
|
|
1996
|
+
"downloadStream: maxBytes", HttpClientError, "httpclient/bad-opts", { permanent: true });
|
|
1834
1997
|
}
|
|
1835
1998
|
|
|
1836
1999
|
function _emitAudit(opts, action, outcome, metadata) {
|
|
@@ -2050,12 +2213,8 @@ function _validateUploadOpts(opts) {
|
|
|
2050
2213
|
}
|
|
2051
2214
|
validateOpts.optionalPositiveFinite(opts.timeoutMs, "uploadMultipartStream: timeoutMs",
|
|
2052
2215
|
HttpClientError, "httpclient/bad-opts");
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
Math.floor(opts.maxBytes) !== opts.maxBytes)) {
|
|
2056
|
-
throw _hcErr("httpclient/bad-opts",
|
|
2057
|
-
"uploadMultipartStream: maxBytes must be a positive finite integer");
|
|
2058
|
-
}
|
|
2216
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
|
|
2217
|
+
"uploadMultipartStream: maxBytes", HttpClientError, "httpclient/bad-opts", { permanent: true });
|
|
2059
2218
|
}
|
|
2060
2219
|
|
|
2061
2220
|
/**
|
|
@@ -542,8 +542,9 @@ function verify(msg, opts) {
|
|
|
542
542
|
var member = digestMembers[di].trim();
|
|
543
543
|
var deq = member.indexOf("=");
|
|
544
544
|
if (deq < 1) continue;
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
var dkv = structuredFields.parseKeyValuePiece(member);
|
|
546
|
+
if (dkv.key !== "sha3-512") continue;
|
|
547
|
+
var memberCanonical = "sha3-512=" + dkv.value.trim();
|
|
547
548
|
// crypto.timingSafeEqual is the length-tolerant constant-time wrapper
|
|
548
549
|
// (returns false for unequal lengths without leaking via a length branch).
|
|
549
550
|
if (bCrypto.timingSafeEqual(memberCanonical, expectedDigest)) { matchedDigest = true; break; }
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
* shaped JSON entries continue to work unchanged.
|
|
46
46
|
*/
|
|
47
47
|
var lazyRequire = require("./lazy-require");
|
|
48
|
+
var boundedMap = require("./bounded-map");
|
|
48
49
|
var { defineClass } = require("./framework-error");
|
|
49
50
|
|
|
50
51
|
var I18nMessageFormatError = defineClass("I18nMessageFormatError",
|
|
@@ -314,12 +315,9 @@ function _skipWs(state) {
|
|
|
314
315
|
var _pluralRulesCache = new Map();
|
|
315
316
|
function _pluralRules(locale, type) {
|
|
316
317
|
var key = locale + "\x1f" + type;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
_pluralRulesCache.set(key, pr);
|
|
321
|
-
}
|
|
322
|
-
return pr;
|
|
318
|
+
return boundedMap.getOrInsert(_pluralRulesCache, key, function () {
|
|
319
|
+
return new Intl.PluralRules(locale, { type: type });
|
|
320
|
+
});
|
|
323
321
|
}
|
|
324
322
|
|
|
325
323
|
function format(template, vars, locale) {
|
|
@@ -382,7 +380,7 @@ function looksLikeMessageFormat(template) {
|
|
|
382
380
|
if (typeof template !== "string") return false;
|
|
383
381
|
// Cheap structural check — full-syntax detection comes from parse()
|
|
384
382
|
// throwing if it isn't valid MessageFormat.
|
|
385
|
-
return /\{[^}]+,\s*(plural|select|selectordinal)\b/.test(template);
|
|
383
|
+
return /\{[^{}]+,\s*(plural|select|selectordinal)\b/.test(template);
|
|
386
384
|
}
|
|
387
385
|
|
|
388
386
|
module.exports = {
|
package/lib/i18n.js
CHANGED
|
@@ -64,7 +64,7 @@ var requestHelpers = require("./request-helpers");
|
|
|
64
64
|
var safeJson = require("./safe-json");
|
|
65
65
|
var validateOpts = require("./validate-opts");
|
|
66
66
|
var { I18nError } = require("./framework-error");
|
|
67
|
-
var { boundedMap } = require("./bounded-map");
|
|
67
|
+
var { boundedMap, getOrInsert } = require("./bounded-map");
|
|
68
68
|
|
|
69
69
|
// Per-instance formatter caches are keyed on (locale, JSON.stringify
|
|
70
70
|
// formatOpts). A fixed `locales` set bounds the locale axis, but operators
|
|
@@ -133,6 +133,80 @@ function _validateLocaleArray(name, value) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Pure resolution-chain builder — the subtag-strip walk shared by the
|
|
137
|
+
// instance lookup and the public localeChain primitive. Start at the requested
|
|
138
|
+
// locale and strip subtag suffixes right-to-left ("pt-BR" -> "pt"); subtag
|
|
139
|
+
// stripping always applies (same language, less specific). Cross-locale
|
|
140
|
+
// fallback (to fallbackLocale, then defaultLocale) fires only when
|
|
141
|
+
// fallbackLocale is non-null — fallbackLocale: null is strict "this locale or
|
|
142
|
+
// miss". No duplicate baseline.
|
|
143
|
+
function _buildLocaleChain(locale, fallbackLocale, defaultLocale) {
|
|
144
|
+
var chain = [];
|
|
145
|
+
var current = locale;
|
|
146
|
+
while (current && chain.indexOf(current) === -1) {
|
|
147
|
+
chain.push(current);
|
|
148
|
+
var dash = current.lastIndexOf("-");
|
|
149
|
+
if (dash === -1) break;
|
|
150
|
+
current = current.slice(0, dash);
|
|
151
|
+
}
|
|
152
|
+
if (fallbackLocale === null) return chain;
|
|
153
|
+
if (chain.indexOf(fallbackLocale) === -1) chain.push(fallbackLocale);
|
|
154
|
+
if (chain.indexOf(defaultLocale) === -1) chain.push(defaultLocale);
|
|
155
|
+
return chain;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @primitive b.i18n.localeChain
|
|
160
|
+
* @signature b.i18n.localeChain(locale, opts)
|
|
161
|
+
* @since 0.15.13
|
|
162
|
+
* @status stable
|
|
163
|
+
* @related b.i18n.create
|
|
164
|
+
*
|
|
165
|
+
* Resolve a requested BCP 47 locale to its ordered fallback chain — the same
|
|
166
|
+
* subtag-strip logic the file-backed `t()` lookup uses, surfaced as a pure,
|
|
167
|
+
* instance-independent function so a data-backed consumer (translations in a
|
|
168
|
+
* database row, a CMS page, a CDN-cached asset) can drive its own per-`(resource,
|
|
169
|
+
* locale, field)` lookups on the framework's BCP 47 fallback instead of
|
|
170
|
+
* re-deriving subtag stripping by hand.
|
|
171
|
+
*
|
|
172
|
+
* Pure: no file loading, no message lookup. The chain is the requested locale,
|
|
173
|
+
* then each subtag-stripped parent ("fr-CA" -> "fr"), then `fallbackLocale`,
|
|
174
|
+
* then `defaultLocale`, de-duplicated. `fallbackLocale: null` gives strict
|
|
175
|
+
* "this locale or its parents only" (no cross-locale jump).
|
|
176
|
+
*
|
|
177
|
+
* @opts
|
|
178
|
+
* defaultLocale: string, // BCP 47; required; the final baseline
|
|
179
|
+
* fallbackLocale: string | null, // null = strict; omitted = defaultLocale
|
|
180
|
+
* locales: string[], // optional configured set; when given, defaultLocale + fallbackLocale must be members
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* b.i18n.localeChain("fr-CA", { defaultLocale: "en", fallbackLocale: "en" });
|
|
184
|
+
* // → ["fr-CA", "fr", "en"]
|
|
185
|
+
* b.i18n.localeChain("zh-Hant-TW", { defaultLocale: "en", fallbackLocale: null });
|
|
186
|
+
* // → ["zh-Hant-TW", "zh-Hant", "zh"] (strict — no jump to en)
|
|
187
|
+
*/
|
|
188
|
+
function localeChain(locale, opts) {
|
|
189
|
+
opts = opts || {};
|
|
190
|
+
_validateLocale("i18n.localeChain: locale", locale);
|
|
191
|
+
_validateLocale("i18n.localeChain: defaultLocale", opts.defaultLocale);
|
|
192
|
+
var defaultLocale = opts.defaultLocale;
|
|
193
|
+
var fallbackLocale = (opts.fallbackLocale === null) ? null
|
|
194
|
+
: ((opts.fallbackLocale === undefined) ? defaultLocale : opts.fallbackLocale);
|
|
195
|
+
if (fallbackLocale !== null) _validateLocale("i18n.localeChain: fallbackLocale", fallbackLocale);
|
|
196
|
+
if (opts.locales !== undefined) {
|
|
197
|
+
_validateLocaleArray("i18n.localeChain: locales", opts.locales);
|
|
198
|
+
if (opts.locales.indexOf(defaultLocale) === -1) {
|
|
199
|
+
throw _err("BAD_OPT", "i18n.localeChain: defaultLocale '" + defaultLocale +
|
|
200
|
+
"' must be one of the configured locales");
|
|
201
|
+
}
|
|
202
|
+
if (fallbackLocale !== null && opts.locales.indexOf(fallbackLocale) === -1) {
|
|
203
|
+
throw _err("BAD_OPT", "i18n.localeChain: fallbackLocale '" + fallbackLocale +
|
|
204
|
+
"' must be one of the configured locales");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return _buildLocaleChain(locale, fallbackLocale, defaultLocale);
|
|
208
|
+
}
|
|
209
|
+
|
|
136
210
|
function _validateInterpolation(value) {
|
|
137
211
|
if (value === undefined) return DEFAULTS.interpolation;
|
|
138
212
|
if (typeof value !== "object" || value === null) {
|
|
@@ -366,13 +440,11 @@ function _makeFormatterCache(make, kind, emitObs) {
|
|
|
366
440
|
return function getFormatter(locale, formatOpts) {
|
|
367
441
|
var optsKey = formatOpts ? JSON.stringify(formatOpts) : "";
|
|
368
442
|
var cacheKey = locale + "\x1f" + optsKey;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
f = make(locale, formatOpts);
|
|
372
|
-
cache.set(cacheKey, f);
|
|
443
|
+
return getOrInsert(cache, cacheKey, function () {
|
|
444
|
+
var f = make(locale, formatOpts);
|
|
373
445
|
emitObs("i18n.format.created", { kind: kind, locale: locale });
|
|
374
|
-
|
|
375
|
-
|
|
446
|
+
return f;
|
|
447
|
+
});
|
|
376
448
|
};
|
|
377
449
|
}
|
|
378
450
|
|
|
@@ -572,25 +644,9 @@ function create(opts) {
|
|
|
572
644
|
}
|
|
573
645
|
|
|
574
646
|
function _localeChain(locale) {
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
// cross-locale jump. Cross-locale fallback (to fallbackLocale, then
|
|
579
|
-
// defaultLocale) only fires when fallbackLocale is non-null —
|
|
580
|
-
// operators who set fallbackLocale: null get strict "this locale or
|
|
581
|
-
// miss" semantics.
|
|
582
|
-
var chain = [];
|
|
583
|
-
var current = locale;
|
|
584
|
-
while (current && chain.indexOf(current) === -1) {
|
|
585
|
-
chain.push(current);
|
|
586
|
-
var dash = current.lastIndexOf("-");
|
|
587
|
-
if (dash === -1) break;
|
|
588
|
-
current = current.slice(0, dash);
|
|
589
|
-
}
|
|
590
|
-
if (fallbackLocale === null) return chain;
|
|
591
|
-
if (chain.indexOf(fallbackLocale) === -1) chain.push(fallbackLocale);
|
|
592
|
-
if (chain.indexOf(defaultLocale) === -1) chain.push(defaultLocale);
|
|
593
|
-
return chain;
|
|
647
|
+
// Delegates to the module-level pure builder; fallbackLocale /
|
|
648
|
+
// defaultLocale are this instance's resolved config.
|
|
649
|
+
return _buildLocaleChain(locale, fallbackLocale, defaultLocale);
|
|
594
650
|
}
|
|
595
651
|
|
|
596
652
|
function _lookupRaw(key, locale) {
|
|
@@ -932,6 +988,7 @@ var messageFormat = require("./i18n-messageformat");
|
|
|
932
988
|
|
|
933
989
|
module.exports = {
|
|
934
990
|
create: create,
|
|
991
|
+
localeChain: localeChain,
|
|
935
992
|
messageFormat: messageFormat,
|
|
936
993
|
I18nError: I18nError,
|
|
937
994
|
DEFAULTS: DEFAULTS,
|
package/lib/iab-tcf.js
CHANGED
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
|
|
97
97
|
var audit = require("./audit");
|
|
98
98
|
var bCrypto = require("./crypto");
|
|
99
|
+
var numericBounds = require("./numeric-bounds");
|
|
99
100
|
var { defineClass } = require("./framework-error");
|
|
100
101
|
var IabTcfError = defineClass("IabTcfError", { alwaysPermanent: true });
|
|
101
102
|
|
|
@@ -521,7 +522,7 @@ function _idArray(x) {
|
|
|
521
522
|
var seen = Object.create(null);
|
|
522
523
|
var out = [];
|
|
523
524
|
list.forEach(function (id) {
|
|
524
|
-
if (
|
|
525
|
+
if (!numericBounds.isPositiveFiniteInt(id)) {
|
|
525
526
|
throw IabTcfError.factory("iab-tcf/bad-value", "iabTcf.encode: vendor/purpose ids must be positive integers, got " + id);
|
|
526
527
|
}
|
|
527
528
|
if (!seen[id]) { seen[id] = 1; out.push(id); }
|
|
@@ -554,7 +555,7 @@ function _decisec(t) {
|
|
|
554
555
|
function _bitWriter() {
|
|
555
556
|
var bits = "";
|
|
556
557
|
function writeInt(v, n) {
|
|
557
|
-
if (
|
|
558
|
+
if (!numericBounds.isNonNegativeFiniteInt(v)) throw IabTcfError.factory("iab-tcf/bad-value", "iabTcf.encode: expected a non-negative integer, got " + v);
|
|
558
559
|
if (v >= Math.pow(2, n)) throw IabTcfError.factory("iab-tcf/value-overflow", "iabTcf.encode: " + v + " does not fit in " + n + " bits");
|
|
559
560
|
bits += v.toString(2).padStart(n, "0");
|
|
560
561
|
}
|