@blamejs/blamejs-shop 0.0.44
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 +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.httpClient
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title Http Client
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Outbound HTTP client with SSRF gate, retry, circuit breaker,
|
|
9
|
+
* wall-clock + idle timeouts, AbortSignal propagation, connection
|
|
10
|
+
* pooling, streaming, and ALPN-negotiated HTTP/2. Built on node:http,
|
|
11
|
+
* node:https, and node:http2 with zero npm runtime dependency.
|
|
12
|
+
*
|
|
13
|
+
* Every outbound request flows through `b.ssrfGuard` out of the box:
|
|
14
|
+
* hostname → DNS lookup is pinned to vetted IP literals, RFC 1918 /
|
|
15
|
+
* loopback / link-local / IPv6 ULA destinations are refused, and the
|
|
16
|
+
* redirect chain is re-validated at every hop so a 302 to
|
|
17
|
+
* `http://169.254.169.254/` (cloud metadata) can't smuggle past the
|
|
18
|
+
* first-hop gate. The same DNS pinning applies to retries — there's
|
|
19
|
+
* no retry path that bypasses the guard.
|
|
20
|
+
*
|
|
21
|
+
* Protocol selection is automatic. HTTPS origins handshake with
|
|
22
|
+
* ALPN `['h2', 'http/1.1']` and cache the resulting transport per
|
|
23
|
+
* `<protocol>//<hostname>:<port>`. While a transport is mid-negotiate
|
|
24
|
+
* the cache holds the in-flight Promise so concurrent calls to a new
|
|
25
|
+
* origin coalesce onto a single connection. h2 GOAWAY or session
|
|
26
|
+
* error evicts the entry; the next request reconnects.
|
|
27
|
+
*
|
|
28
|
+
* Resiliency defaults: TLS 1.3 minimum, PQC-preferred `ecdhCurve`
|
|
29
|
+
* group order, split wall-clock vs zero-progress idle timeouts,
|
|
30
|
+
* request-body stream errors propagated to the returned Promise,
|
|
31
|
+
* and h2 stream cancellation via NGHTTP2_CANCEL (clean, not
|
|
32
|
+
* `stream.destroy`) when the AbortSignal fires.
|
|
33
|
+
*
|
|
34
|
+
* @card
|
|
35
|
+
* Outbound HTTP client with SSRF gate, retry, circuit breaker, wall-clock + idle timeouts, AbortSignal propagation, connection pooling, streaming, and ALPN-negotiated HTTP/2.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
var nodeFs = require("node:fs");
|
|
39
|
+
var http = require("node:http");
|
|
40
|
+
var https = require("node:https");
|
|
41
|
+
var http2 = require("node:http2");
|
|
42
|
+
var nodeCrypto = require("node:crypto");
|
|
43
|
+
var nodePath = require("node:path");
|
|
44
|
+
var nodeStream = require("node:stream");
|
|
45
|
+
var streamPromises = require("node:stream/promises");
|
|
46
|
+
var { URL } = require("node:url");
|
|
47
|
+
var atomicFile = require("./atomic-file");
|
|
48
|
+
var C = require("./constants");
|
|
49
|
+
var bCrypto = require("./crypto");
|
|
50
|
+
var pqcAgent = require("./pqc-agent");
|
|
51
|
+
var safeAsync = require("./safe-async");
|
|
52
|
+
var safeBuffer = require("./safe-buffer");
|
|
53
|
+
var safeUrl = require("./safe-url");
|
|
54
|
+
var ssrfGuard = require("./ssrf-guard");
|
|
55
|
+
var networkProxy = require("./network-proxy");
|
|
56
|
+
var validateOpts = require("./validate-opts");
|
|
57
|
+
var { FrameworkError, HttpClientError } = require("./framework-error");
|
|
58
|
+
|
|
59
|
+
// Per-origin transport cache. Entry is either the resolved transport
|
|
60
|
+
// object or a pending Promise that resolves to one. The Promise form
|
|
61
|
+
// lets concurrent calls to a new origin coalesce on the same connect.
|
|
62
|
+
//
|
|
63
|
+
// Transport shapes the cache currently holds:
|
|
64
|
+
//
|
|
65
|
+
// { kind: "h1", lib, agent } — node:http(s) + keepAlive Agent
|
|
66
|
+
// { kind: "h2", session } — node:http2 ClientHttp2Session
|
|
67
|
+
//
|
|
68
|
+
// Reserved for the future (when node:http3 ships stable — currently
|
|
69
|
+
// behind --experimental-quic, no http3 module yet):
|
|
70
|
+
//
|
|
71
|
+
// { kind: "h3", session } — node:http3 ClientHttp3Session
|
|
72
|
+
//
|
|
73
|
+
// Adding the h3 case won't change the caller-facing surface: ALPN
|
|
74
|
+
// negotiation gains "h3" as the highest-preference protocol over QUIC,
|
|
75
|
+
// _getTransport branches on the resolved transport, and a new
|
|
76
|
+
// _requestH3 mirrors _requestH2's stream-based shape. h3's design
|
|
77
|
+
// gives 0-RTT first-class (vs. h1/h2 where 0-RTT is opaque under
|
|
78
|
+
// node's TLS layer — see TLS_SESSION_RESUMPTION_NOTES below).
|
|
79
|
+
var _transports = new Map();
|
|
80
|
+
|
|
81
|
+
// TLS session resumption notes — what's automatic vs. what's not
|
|
82
|
+
// exposed by Node's public API:
|
|
83
|
+
//
|
|
84
|
+
// keepAlive Agent (h1) / long-lived ClientHttp2Session (h2) means
|
|
85
|
+
// the WARM-CONNECTION case is zero-handshake — better than 0-RTT.
|
|
86
|
+
// We pay the TLS handshake once per origin, then amortize.
|
|
87
|
+
//
|
|
88
|
+
// When a pool socket is recycled, node's tls layer caches session
|
|
89
|
+
// tickets and does 1-RTT resumption automatically. We don't
|
|
90
|
+
// expose 0-RTT (early_data) — node's https.Agent has no clean API
|
|
91
|
+
// for it, and 0-RTT is REPLAY-RISKY for non-idempotent requests
|
|
92
|
+
// (server can't distinguish original from replay until the
|
|
93
|
+
// handshake completes). Operators who need 0-RTT for a specific
|
|
94
|
+
// idempotent path can pass their own agent via opts.agent.
|
|
95
|
+
//
|
|
96
|
+
// QUIC/h3 changes this calculus: 0-RTT is a first-class feature
|
|
97
|
+
// built into the protocol, with replay protection at the QUIC
|
|
98
|
+
// layer. The framework's `b.httpClient` is HTTP/1.1 + HTTP/2 only;
|
|
99
|
+
// operators wanting h3 wire their own client.
|
|
100
|
+
//
|
|
101
|
+
// QUIC retry / address-validation (RFC 9000 §8 + RFC 9001 §6) is
|
|
102
|
+
// deferred-with-condition: outbound h3 negotiation re-opens when
|
|
103
|
+
// Node's `--experimental-quic` graduates to stable and ships a
|
|
104
|
+
// `node:http3` module. The escape hatch today is `opts.agent` —
|
|
105
|
+
// operators on internal-mesh deployments that already terminate h3
|
|
106
|
+
// pass their own h3 agent rather than the framework rolling its
|
|
107
|
+
// own implementation under an experimental Node flag. SECURITY.md
|
|
108
|
+
// "Watch list" tracks the re-open trigger.
|
|
109
|
+
|
|
110
|
+
// Pool tuning for the HTTP-client transport cache. Keep-alive is
|
|
111
|
+
// shorter than the standalone pqc-agent default (1s vs 30s) because
|
|
112
|
+
// the cache layer manages its own warm-connection reuse and we want
|
|
113
|
+
// idle sockets reaped quickly between bursts. ecdhCurve / minVersion
|
|
114
|
+
// come from pqc-agent and cannot be set here — the framework's
|
|
115
|
+
// PQC-only TLS posture is one place, in lib/pqc-agent.js.
|
|
116
|
+
//
|
|
117
|
+
// Operators tune at boot via `b.httpClient.configurePool({...})`.
|
|
118
|
+
// Existing transports stay on whichever values were active when they
|
|
119
|
+
// were created — reconfigure runs before any outbound request to take
|
|
120
|
+
// effect on the per-origin cache.
|
|
121
|
+
var DEFAULT_AGENT_OPTS = Object.freeze({
|
|
122
|
+
keepAlive: true,
|
|
123
|
+
keepAliveMsecs: C.TIME.seconds(1),
|
|
124
|
+
maxSockets: C.BYTES.bytes(16),
|
|
125
|
+
maxFreeSockets: C.BYTES.bytes(8),
|
|
126
|
+
scheduling: "lifo",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
var HTTP_CLIENT_AGENT_OPTS = Object.assign({}, DEFAULT_AGENT_OPTS);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @primitive b.httpClient.configurePool
|
|
133
|
+
* @signature b.httpClient.configurePool(opts)
|
|
134
|
+
* @since 0.1.0
|
|
135
|
+
* @status stable
|
|
136
|
+
* @related b.httpClient.request
|
|
137
|
+
*
|
|
138
|
+
* Updates the keepAlive Agent options used for new h1 transports and
|
|
139
|
+
* tears down the per-origin transport cache so subsequent requests
|
|
140
|
+
* pick up the fresh values. Existing in-flight responses keep their
|
|
141
|
+
* old transport. Throws on unknown keys, non-positive integers, or a
|
|
142
|
+
* non-boolean `keepAlive`. Use at boot when the default 16/8 socket
|
|
143
|
+
* caps don't match the operator's downstream concurrency budget.
|
|
144
|
+
*
|
|
145
|
+
* @opts
|
|
146
|
+
* keepAlive: true, // boolean — whether to reuse sockets
|
|
147
|
+
* keepAliveMsecs: 1000, // positive integer ms between keep-alive probes
|
|
148
|
+
* maxSockets: 16, // positive integer — concurrent sockets per origin
|
|
149
|
+
* maxFreeSockets: 8, // positive integer — idle sockets retained per origin
|
|
150
|
+
* scheduling: "lifo", // "lifo" | "fifo"
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* b.httpClient.configurePool({ maxSockets: 64, maxFreeSockets: 32 });
|
|
154
|
+
* // → undefined (cache cleared; next request builds a 64-socket pool)
|
|
155
|
+
*/
|
|
156
|
+
function configurePool(opts) {
|
|
157
|
+
if (!opts || typeof opts !== "object") {
|
|
158
|
+
throw new Error("httpClient.configurePool: opts must be an object");
|
|
159
|
+
}
|
|
160
|
+
var allowed = ["keepAlive", "keepAliveMsecs", "maxSockets", "maxFreeSockets", "scheduling"];
|
|
161
|
+
for (var k in opts) {
|
|
162
|
+
if (!Object.prototype.hasOwnProperty.call(opts, k)) continue;
|
|
163
|
+
if (allowed.indexOf(k) === -1) {
|
|
164
|
+
throw new Error("httpClient.configurePool: unknown option '" + k +
|
|
165
|
+
"'. Allowed: " + allowed.join(", "));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function _requirePositiveInt(name, value) {
|
|
169
|
+
if (typeof value !== "number" || !isFinite(value) || value <= 0 || Math.floor(value) !== value) {
|
|
170
|
+
throw new Error("httpClient.configurePool: " + name +
|
|
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);
|
|
177
|
+
if (opts.keepAlive !== undefined && typeof opts.keepAlive !== "boolean") {
|
|
178
|
+
throw new Error("httpClient.configurePool: keepAlive must be a boolean");
|
|
179
|
+
}
|
|
180
|
+
if (opts.scheduling !== undefined && opts.scheduling !== "lifo" && opts.scheduling !== "fifo") {
|
|
181
|
+
throw new Error("httpClient.configurePool: scheduling must be 'lifo' or 'fifo'");
|
|
182
|
+
}
|
|
183
|
+
Object.assign(HTTP_CLIENT_AGENT_OPTS, opts);
|
|
184
|
+
// Existing transports keep their old values (Agent constructor
|
|
185
|
+
// copies). Drop the per-origin cache + tear down idle sockets so
|
|
186
|
+
// subsequent requests build fresh transports with the new opts.
|
|
187
|
+
_transports.forEach(function (t) {
|
|
188
|
+
if (t && t.kind === "h1" && t.agent && typeof t.agent.destroy === "function") {
|
|
189
|
+
try { t.agent.destroy(); } catch (_e) { /* best-effort agent teardown */ }
|
|
190
|
+
}
|
|
191
|
+
if (t && t.kind === "h2" && t.session) {
|
|
192
|
+
_tearDownH2Session(t.session);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
_transports.clear();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// HTTP/2 session teardown — see lib/http2-teardown.js for the full
|
|
199
|
+
// rationale. Centralised so any future sink / pool teardown gets the
|
|
200
|
+
// same close()-then-destroy() discipline.
|
|
201
|
+
var _tearDownH2Session = require("./http2-teardown").tearDownH2Session;
|
|
202
|
+
|
|
203
|
+
// h2 session connect options. Same TLS posture as h1 Agent.
|
|
204
|
+
var DEFAULT_H2_TLS_OPTS = {
|
|
205
|
+
ALPNProtocols: ["h2", "http/1.1"],
|
|
206
|
+
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
207
|
+
minVersion: "TLSv1.3",
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
var DEFAULT_CONTROL_PLANE_CAP = C.BYTES.mib(16);
|
|
211
|
+
var DEFAULT_GET_CAP = C.BYTES.gib(1);
|
|
212
|
+
var DEFAULT_IDLE_TIMEOUT_MS = C.TIME.seconds(30);
|
|
213
|
+
|
|
214
|
+
// h2 session idle close. After this much idle time with no streams,
|
|
215
|
+
// close the session — long-running processes don't pin one TLS
|
|
216
|
+
// connection forever.
|
|
217
|
+
var H2_SESSION_IDLE_TIMEOUT_MS = C.TIME.minutes(5);
|
|
218
|
+
|
|
219
|
+
// IANA-assigned default ports per RFC 9110 §4.2.
|
|
220
|
+
var DEFAULT_HTTPS_PORT = 443;
|
|
221
|
+
var DEFAULT_HTTP_PORT = C.BYTES.bytes(80);
|
|
222
|
+
|
|
223
|
+
function _defaultPortFor(u) {
|
|
224
|
+
return u.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function _originKey(u) {
|
|
228
|
+
return u.protocol + "//" + u.hostname + ":" + (u.port || _defaultPortFor(u));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _makeH1Transport(u, ips) {
|
|
232
|
+
var lib = u.protocol === "https:" ? https : http;
|
|
233
|
+
// HTTPS path goes through pqcAgent.create so the framework's PQC-only
|
|
234
|
+
// posture is enforced via the single primitive. Cleartext HTTP stays
|
|
235
|
+
// on http.Agent because there's no TLS posture to enforce.
|
|
236
|
+
var agent = u.protocol === "https:"
|
|
237
|
+
? pqcAgent.create(HTTP_CLIENT_AGENT_OPTS)
|
|
238
|
+
: new lib.Agent(HTTP_CLIENT_AGENT_OPTS);
|
|
239
|
+
return { kind: "h1", lib: lib, agent: agent, lookup: _pinnedLookupFor(ips) };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Build a `lookup` callback that pins outbound connections to IPs the
|
|
243
|
+
// SSRF guard already validated. Closes the TOCTOU window between
|
|
244
|
+
// SSRF resolution and the kernel's connect — without this, a hostile
|
|
245
|
+
// (or compromised) DNS could rotate the answer between guard-check
|
|
246
|
+
// and connect-time and route the request to a private / metadata IP
|
|
247
|
+
// that bypassed the gate. ips comes from `ssrfGuard.checkUrl` — its
|
|
248
|
+
// classification ran on these exact addresses.
|
|
249
|
+
function _pinnedLookupFor(ips) {
|
|
250
|
+
if (!Array.isArray(ips) || ips.length === 0) return undefined;
|
|
251
|
+
var families = ips.map(function (i) { return { address: i.address, family: i.family || 4 }; });
|
|
252
|
+
return function pinnedLookup(hostname, options, callback) {
|
|
253
|
+
if (typeof options === "function") { callback = options; options = {}; }
|
|
254
|
+
options = options || {};
|
|
255
|
+
if (options.all) {
|
|
256
|
+
callback(null, families);
|
|
257
|
+
} else {
|
|
258
|
+
callback(null, families[0].address, families[0].family);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Connect an h2 session to an HTTPS origin via ALPN. If the server picks
|
|
264
|
+
// http/1.1, fall back to an h1 transport for that origin.
|
|
265
|
+
function _connectHttpsWithAlpn(u, ips) {
|
|
266
|
+
return new Promise(function (resolve, reject) {
|
|
267
|
+
var connectOpts = Object.assign({}, DEFAULT_H2_TLS_OPTS);
|
|
268
|
+
var pinned = _pinnedLookupFor(ips);
|
|
269
|
+
if (pinned) connectOpts.lookup = pinned;
|
|
270
|
+
var session = http2.connect(u.protocol + "//" + u.host, connectOpts);
|
|
271
|
+
var settled = false;
|
|
272
|
+
function _done(t) { if (!settled) { settled = true; resolve(t); } }
|
|
273
|
+
function _fail(err) { if (!settled) { settled = true; reject(err); } }
|
|
274
|
+
|
|
275
|
+
session.once("connect", function () {
|
|
276
|
+
var alpn = session.alpnProtocol;
|
|
277
|
+
if (alpn === "h2") {
|
|
278
|
+
_wireH2Session(session, _originKey(u));
|
|
279
|
+
_done({ kind: "h2", session: session });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Server picked http/1.1 — close the h2 session, return h1 transport.
|
|
283
|
+
_tearDownH2Session(session);
|
|
284
|
+
_done(_makeH1Transport(u, ips));
|
|
285
|
+
});
|
|
286
|
+
session.once("error", function (err) {
|
|
287
|
+
_tearDownH2Session(session);
|
|
288
|
+
_fail(err);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Connect an h2c session (cleartext h2). No ALPN, no fallback — caller
|
|
294
|
+
// has attested via preferH2 that the server speaks h2c.
|
|
295
|
+
function _connectH2c(u, ips) {
|
|
296
|
+
return new Promise(function (resolve, reject) {
|
|
297
|
+
var connectOpts = {};
|
|
298
|
+
var pinned = _pinnedLookupFor(ips);
|
|
299
|
+
if (pinned) connectOpts.lookup = pinned;
|
|
300
|
+
var session = http2.connect(u.protocol + "//" + u.host, connectOpts);
|
|
301
|
+
session.once("connect", function () {
|
|
302
|
+
_wireH2Session(session, _originKey(u));
|
|
303
|
+
resolve({ kind: "h2", session: session });
|
|
304
|
+
});
|
|
305
|
+
session.once("error", function (err) {
|
|
306
|
+
_tearDownH2Session(session);
|
|
307
|
+
reject(err);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Common h2 session wiring — idle close + cache eviction on error/close.
|
|
313
|
+
function _wireH2Session(session, key) {
|
|
314
|
+
session.setTimeout(H2_SESSION_IDLE_TIMEOUT_MS, function () {
|
|
315
|
+
_tearDownH2Session(session);
|
|
316
|
+
});
|
|
317
|
+
session.once("close", function () { _transports.delete(key); });
|
|
318
|
+
session.once("error", function () { _transports.delete(key); });
|
|
319
|
+
session.once("goaway", function () {
|
|
320
|
+
// Server signalling 'no new streams' — let in-flight finish, evict cache.
|
|
321
|
+
_transports.delete(key);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Async transport selection. Returns Promise<transport>. `ips` is the
|
|
326
|
+
// validated address list returned by `ssrfGuard.checkUrl`; the transport
|
|
327
|
+
// uses it to pin connections so a hostile DNS rebind can't redirect
|
|
328
|
+
// the actual TCP connect to a private / metadata IP.
|
|
329
|
+
function _getTransport(u, opts, ips) {
|
|
330
|
+
var key = _originKey(u);
|
|
331
|
+
var cached = _transports.get(key);
|
|
332
|
+
if (cached) {
|
|
333
|
+
// Could be a resolved transport OR a pending Promise. Cached
|
|
334
|
+
// transports keep whatever IP pinning was set when they were
|
|
335
|
+
// first created — subsequent SSRF checks still gate the request,
|
|
336
|
+
// and the transport's TCP socket is bound to its original IP.
|
|
337
|
+
return Promise.resolve(cached);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
var promise;
|
|
341
|
+
if (u.protocol === "https:") {
|
|
342
|
+
promise = _connectHttpsWithAlpn(u, ips);
|
|
343
|
+
} else if (opts && opts.preferH2) {
|
|
344
|
+
promise = _connectH2c(u, ips);
|
|
345
|
+
} else {
|
|
346
|
+
// HTTP without preferH2 → h1 only.
|
|
347
|
+
promise = Promise.resolve(_makeH1Transport(u, ips));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Cache the in-flight Promise immediately so concurrent calls
|
|
351
|
+
// coalesce. On resolve, replace with the transport. On reject, evict.
|
|
352
|
+
_transports.set(key, promise);
|
|
353
|
+
promise.then(
|
|
354
|
+
function (t) { _transports.set(key, t); },
|
|
355
|
+
function (_err) { _transports.delete(key); }
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return promise;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function _makeError(errorClass, code, message, permanent, statusCode) {
|
|
362
|
+
if (!errorClass) return new FrameworkError(message, code);
|
|
363
|
+
return new errorClass(code, message, permanent, statusCode);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// RFC 9110 §15.5 4xx codes that are NOT permanent (request-timeout,
|
|
367
|
+
// too-early, too-many-requests — operator should retry).
|
|
368
|
+
var STATUS_REQUEST_TIMEOUT = C.BYTES.bytes(408);
|
|
369
|
+
var STATUS_TOO_EARLY = 425;
|
|
370
|
+
var STATUS_TOO_MANY_REQUESTS = 429;
|
|
371
|
+
|
|
372
|
+
function _isPermanentStatus(statusCode) {
|
|
373
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
374
|
+
return statusCode !== STATUS_REQUEST_TIMEOUT &&
|
|
375
|
+
statusCode !== STATUS_TOO_EARLY &&
|
|
376
|
+
statusCode !== STATUS_TOO_MANY_REQUESTS;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// h2 sends headers as lowercased keys plus :method / :path / :scheme /
|
|
382
|
+
// :authority pseudo-headers. Convert from h1-shaped headers.
|
|
383
|
+
function _toH2Headers(method, u, headers) {
|
|
384
|
+
var h2Headers = Object.create(null);
|
|
385
|
+
h2Headers[":method"] = method;
|
|
386
|
+
h2Headers[":path"] = u.pathname + (u.search || "");
|
|
387
|
+
h2Headers[":scheme"] = u.protocol === "https:" ? "https" : "http";
|
|
388
|
+
h2Headers[":authority"] = u.host;
|
|
389
|
+
var sawAcceptEncoding = false;
|
|
390
|
+
for (var k in headers) {
|
|
391
|
+
if (!Object.prototype.hasOwnProperty.call(headers, k)) continue;
|
|
392
|
+
var lk = k.toLowerCase();
|
|
393
|
+
// h2 forbids the connection-specific headers
|
|
394
|
+
if (lk === "connection" || lk === "host" ||
|
|
395
|
+
lk === "keep-alive" || lk === "transfer-encoding" ||
|
|
396
|
+
lk === "upgrade" || lk === "proxy-connection") continue;
|
|
397
|
+
if (lk === "accept-encoding") sawAcceptEncoding = true;
|
|
398
|
+
h2Headers[lk] = headers[k];
|
|
399
|
+
}
|
|
400
|
+
// CVE-2026-22036 mitigation — same identity default as the h1 path.
|
|
401
|
+
// Refuse compressed responses unless the operator explicitly opts in.
|
|
402
|
+
if (!sawAcceptEncoding) h2Headers["accept-encoding"] = "identity";
|
|
403
|
+
return h2Headers;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function _fromH2Headers(h2Headers) {
|
|
407
|
+
// Strip pseudo-headers from the response — caller doesn't want them
|
|
408
|
+
// mixed with normal headers.
|
|
409
|
+
var out = {};
|
|
410
|
+
for (var k in h2Headers) {
|
|
411
|
+
if (!Object.prototype.hasOwnProperty.call(h2Headers, k)) continue;
|
|
412
|
+
if (k.charAt(0) === ":") continue;
|
|
413
|
+
out[k] = h2Headers[k];
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---- request() ----
|
|
419
|
+
|
|
420
|
+
var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
421
|
+
|
|
422
|
+
// http-client-cookie-jar is exposed on b.httpClient.cookieJar via index.js.
|
|
423
|
+
// http-client itself only consumes the jar shape passed by operators via
|
|
424
|
+
// the request opt; no direct require is needed here.
|
|
425
|
+
|
|
426
|
+
// Merge a jar-derived Cookie header with any caller-supplied Cookie
|
|
427
|
+
// header. Operators who override Cookie explicitly always win — the jar
|
|
428
|
+
// supplements rather than replaces.
|
|
429
|
+
function _attachJarCookie(headers, jar, url) {
|
|
430
|
+
if (!jar) return headers;
|
|
431
|
+
var jarHeader = jar.cookieHeaderFor(url);
|
|
432
|
+
if (!jarHeader) return headers;
|
|
433
|
+
var merged = Object.assign({}, headers || {});
|
|
434
|
+
var existing = null;
|
|
435
|
+
var keys = Object.keys(merged);
|
|
436
|
+
for (var i = 0; i < keys.length; i++) {
|
|
437
|
+
if (keys[i].toLowerCase() === "cookie") { existing = keys[i]; break; }
|
|
438
|
+
}
|
|
439
|
+
if (existing) merged[existing] = merged[existing] + "; " + jarHeader;
|
|
440
|
+
else merged.Cookie = jarHeader;
|
|
441
|
+
return merged;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Build a multipart/form-data body from { fields, files } shape.
|
|
445
|
+
// Mirrors the wire format that lib/middleware/body-parser.js's multipart
|
|
446
|
+
// parser accepts so round-trip from one blamejs app's outbound to
|
|
447
|
+
// another's inbound is exact.
|
|
448
|
+
//
|
|
449
|
+
// Two output shapes:
|
|
450
|
+
//
|
|
451
|
+
// - { boundary, body: Buffer, contentLength }
|
|
452
|
+
// When every file entry is a Buffer / string (size known
|
|
453
|
+
// up front) and no operator opted into streaming, the result
|
|
454
|
+
// is a fully-materialized body. Smaller payloads avoid the
|
|
455
|
+
// streaming overhead and let HTTP/1.1 KeepAlive reuse with a
|
|
456
|
+
// known Content-Length.
|
|
457
|
+
//
|
|
458
|
+
// - { boundary, body: Readable, contentLength }
|
|
459
|
+
// When at least one file entry is `{ filePath }` / `{ stream }`
|
|
460
|
+
// OR opts.streaming === true, the result is a Readable that
|
|
461
|
+
// emits boundary headers + content + CRLF in order. Avoids the
|
|
462
|
+
// Buffer.concat() OOM class on large uploads. contentLength is
|
|
463
|
+
// a finite number when every source's size is statically
|
|
464
|
+
// resolvable (Buffer length, nodeFs.statSync().size, opts.size on
|
|
465
|
+
// a stream entry); null otherwise — caller falls back to
|
|
466
|
+
// chunked transfer.
|
|
467
|
+
//
|
|
468
|
+
// File entry shapes (all require `field`):
|
|
469
|
+
//
|
|
470
|
+
// { field, content: Buffer | string } — in-memory (existing)
|
|
471
|
+
// { field, filePath: string } — stream-from-disk
|
|
472
|
+
// { field, stream: Readable, size?: number } — operator-supplied stream
|
|
473
|
+
//
|
|
474
|
+
// `filename` and `contentType` apply to all three shapes; for
|
|
475
|
+
// `filePath` entries, `filename` defaults to path.basename(filePath).
|
|
476
|
+
function _buildMultipartBody(spec) {
|
|
477
|
+
var boundary = "----blamejs-mp-" + bCrypto.generateToken(C.BYTES.bytes(16));
|
|
478
|
+
var CRLF = "\r\n";
|
|
479
|
+
var nodeFs = require("node:fs"); // allow:inline-require — only on multipart paths that touch the filesystem
|
|
480
|
+
var path = require("node:path"); // allow:inline-require — same
|
|
481
|
+
var nodeStream = require("node:stream"); // allow:inline-require — Readable subclass only when streaming
|
|
482
|
+
|
|
483
|
+
// Each entry is { headerBytes, source } where source is one of:
|
|
484
|
+
// { kind: "buffer", buf: Buffer }
|
|
485
|
+
// { kind: "filePath", filePath: string, size: number }
|
|
486
|
+
// { kind: "stream", stream: Readable, size: number | null }
|
|
487
|
+
var entries = [];
|
|
488
|
+
var anyStreaming = false;
|
|
489
|
+
var totalSize = 0;
|
|
490
|
+
var sizeKnown = true;
|
|
491
|
+
|
|
492
|
+
function _entryHeaderBytes(disposition, contentType) {
|
|
493
|
+
var head = "--" + boundary + CRLF + disposition + CRLF;
|
|
494
|
+
if (contentType) head += "Content-Type: " + contentType + CRLF;
|
|
495
|
+
head += CRLF;
|
|
496
|
+
return Buffer.from(head, "utf8");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function _addEntry(headerBytes, source) {
|
|
500
|
+
entries.push({ header: headerBytes, source: source });
|
|
501
|
+
totalSize += headerBytes.length;
|
|
502
|
+
if (source.kind === "buffer") {
|
|
503
|
+
totalSize += source.buf.length;
|
|
504
|
+
} else if (typeof source.size === "number" && isFinite(source.size) && source.size >= 0) {
|
|
505
|
+
totalSize += source.size;
|
|
506
|
+
} else {
|
|
507
|
+
sizeKnown = false;
|
|
508
|
+
}
|
|
509
|
+
totalSize += CRLF.length;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function _pushField(name, value) {
|
|
513
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
514
|
+
throw new Error("multipart: field name must be a non-empty string");
|
|
515
|
+
}
|
|
516
|
+
var disposition = 'Content-Disposition: form-data; name="' + name + '"';
|
|
517
|
+
var head = _entryHeaderBytes(disposition, null);
|
|
518
|
+
var bodyBuf = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");
|
|
519
|
+
_addEntry(head, { kind: "buffer", buf: bodyBuf });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function _pushFile(file) {
|
|
523
|
+
if (!file || typeof file !== "object") throw new Error("multipart: file entries must be objects");
|
|
524
|
+
if (typeof file.field !== "string" || file.field.length === 0) {
|
|
525
|
+
throw new Error("multipart: file.field must be a non-empty string");
|
|
526
|
+
}
|
|
527
|
+
var hasContent = file.content !== undefined && file.content !== null;
|
|
528
|
+
var hasFilePath = typeof file.filePath === "string" && file.filePath.length > 0;
|
|
529
|
+
var hasStream = file.stream && typeof file.stream.pipe === "function";
|
|
530
|
+
var sourceCount = (hasContent ? 1 : 0) + (hasFilePath ? 1 : 0) + (hasStream ? 1 : 0);
|
|
531
|
+
if (sourceCount === 0) {
|
|
532
|
+
throw new Error("multipart: file entry requires one of { content, filePath, stream }");
|
|
533
|
+
}
|
|
534
|
+
if (sourceCount > 1) {
|
|
535
|
+
throw new Error("multipart: file entry must have exactly one of { content, filePath, stream }");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
var filename;
|
|
539
|
+
if (typeof file.filename === "string" && file.filename.length > 0) {
|
|
540
|
+
filename = file.filename;
|
|
541
|
+
} else if (hasFilePath) {
|
|
542
|
+
filename = path.basename(file.filePath);
|
|
543
|
+
} else {
|
|
544
|
+
filename = "blob";
|
|
545
|
+
}
|
|
546
|
+
var mimeType = file.contentType || file.mimeType || "application/octet-stream";
|
|
547
|
+
var disposition = 'Content-Disposition: form-data; name="' + file.field + '"' +
|
|
548
|
+
'; filename="' + filename.replace(/"/g, "%22") + '"';
|
|
549
|
+
var head = _entryHeaderBytes(disposition, mimeType);
|
|
550
|
+
|
|
551
|
+
if (hasContent) {
|
|
552
|
+
var content = file.content;
|
|
553
|
+
if (typeof content === "string") content = Buffer.from(content, "utf8");
|
|
554
|
+
if (!Buffer.isBuffer(content)) {
|
|
555
|
+
throw new Error("multipart: file.content must be a Buffer or string");
|
|
556
|
+
}
|
|
557
|
+
_addEntry(head, { kind: "buffer", buf: content });
|
|
558
|
+
} else if (hasFilePath) {
|
|
559
|
+
anyStreaming = true;
|
|
560
|
+
var st;
|
|
561
|
+
try { st = nodeFs.statSync(file.filePath); }
|
|
562
|
+
catch (e) { throw new Error("multipart: file.filePath not readable: " + e.message); }
|
|
563
|
+
if (!st.isFile()) throw new Error("multipart: file.filePath is not a regular file");
|
|
564
|
+
_addEntry(head, { kind: "filePath", filePath: file.filePath, size: st.size });
|
|
565
|
+
} else {
|
|
566
|
+
anyStreaming = true;
|
|
567
|
+
var streamSize = (typeof file.size === "number" && isFinite(file.size) && file.size >= 0)
|
|
568
|
+
? file.size : null;
|
|
569
|
+
_addEntry(head, { kind: "stream", stream: file.stream, size: streamSize });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (spec && spec.fields && typeof spec.fields === "object") {
|
|
574
|
+
var keys = Object.keys(spec.fields);
|
|
575
|
+
for (var i = 0; i < keys.length; i++) {
|
|
576
|
+
var k = keys[i];
|
|
577
|
+
var v = spec.fields[k];
|
|
578
|
+
if (Array.isArray(v)) {
|
|
579
|
+
for (var j = 0; j < v.length; j++) _pushField(k, v[j]);
|
|
580
|
+
} else {
|
|
581
|
+
_pushField(k, v);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (spec && Array.isArray(spec.files)) {
|
|
586
|
+
for (var fi = 0; fi < spec.files.length; fi++) _pushFile(spec.files[fi]);
|
|
587
|
+
}
|
|
588
|
+
var trailer = Buffer.from("--" + boundary + "--" + CRLF, "utf8");
|
|
589
|
+
totalSize += trailer.length;
|
|
590
|
+
|
|
591
|
+
// All-buffer fast path — return a fully-materialized body when no
|
|
592
|
+
// streaming sources are involved AND the operator didn't ask for
|
|
593
|
+
// streaming explicitly. Existing callers that pass small in-memory
|
|
594
|
+
// payloads keep the buffer codepath.
|
|
595
|
+
if (!anyStreaming && !(spec && spec.streaming === true)) {
|
|
596
|
+
var parts = [];
|
|
597
|
+
for (var ei = 0; ei < entries.length; ei++) {
|
|
598
|
+
parts.push(entries[ei].header);
|
|
599
|
+
parts.push(entries[ei].source.buf);
|
|
600
|
+
parts.push(Buffer.from(CRLF, "utf8"));
|
|
601
|
+
}
|
|
602
|
+
parts.push(trailer);
|
|
603
|
+
return { boundary: boundary, body: Buffer.concat(parts), contentLength: totalSize };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Streaming path — produce a Readable from an async iterator that
|
|
607
|
+
// yields the bytes for each entry in order.
|
|
608
|
+
var crlfBuf = Buffer.from(CRLF, "utf8");
|
|
609
|
+
async function* _iter() {
|
|
610
|
+
for (var ix = 0; ix < entries.length; ix++) {
|
|
611
|
+
var entry = entries[ix];
|
|
612
|
+
yield entry.header;
|
|
613
|
+
if (entry.source.kind === "buffer") {
|
|
614
|
+
yield entry.source.buf;
|
|
615
|
+
} else if (entry.source.kind === "filePath") {
|
|
616
|
+
var rs = nodeFs.createReadStream(entry.source.filePath);
|
|
617
|
+
try {
|
|
618
|
+
for await (var chunk of rs) yield chunk;
|
|
619
|
+
} finally {
|
|
620
|
+
try { rs.destroy(); } catch (_e) { /* best-effort cleanup */ }
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
// operator-supplied stream
|
|
624
|
+
for await (var chunk2 of entry.source.stream) yield chunk2;
|
|
625
|
+
}
|
|
626
|
+
yield crlfBuf;
|
|
627
|
+
}
|
|
628
|
+
yield trailer;
|
|
629
|
+
}
|
|
630
|
+
var body = nodeStream.Readable.from(_iter());
|
|
631
|
+
return {
|
|
632
|
+
boundary: boundary,
|
|
633
|
+
body: body,
|
|
634
|
+
contentLength: sizeKnown ? totalSize : null,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Headers stripped on cross-origin redirect to defend against accidental
|
|
639
|
+
// credential exfiltration. Lower-case for header-map comparison.
|
|
640
|
+
var SENSITIVE_HEADERS_LC = ["authorization", "cookie", "proxy-authorization"];
|
|
641
|
+
|
|
642
|
+
function _stripCrossOriginAuth(headers) {
|
|
643
|
+
var out = {};
|
|
644
|
+
var keys = Object.keys(headers);
|
|
645
|
+
for (var i = 0; i < keys.length; i++) {
|
|
646
|
+
if (SENSITIVE_HEADERS_LC.indexOf(keys[i].toLowerCase()) !== -1) continue;
|
|
647
|
+
out[keys[i]] = headers[keys[i]];
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* @primitive b.httpClient.request
|
|
654
|
+
* @signature b.httpClient.request(opts)
|
|
655
|
+
* @since 0.1.0
|
|
656
|
+
* @status stable
|
|
657
|
+
* @related b.httpClient.downloadStream, b.httpClient.uploadMultipartStream, b.ssrfGuard
|
|
658
|
+
*
|
|
659
|
+
* Promise-returning, AbortSignal-aware HTTP request. Negotiates h2 /
|
|
660
|
+
* h1 per-origin via ALPN, reuses transports from the cache, runs every
|
|
661
|
+
* destination through `b.ssrfGuard` before connecting, and re-validates
|
|
662
|
+
* each redirect hop. Returns `{ statusCode, headers, body }` for the
|
|
663
|
+
* default `"buffer"` mode; `"stream"` returns a Readable for the body.
|
|
664
|
+
* Sensitive headers (Authorization / Cookie / Proxy-Authorization) are
|
|
665
|
+
* stripped on cross-origin redirect. Body-stream errors propagate to
|
|
666
|
+
* the rejected Promise.
|
|
667
|
+
*
|
|
668
|
+
* @opts
|
|
669
|
+
* method: "GET", // HTTP method
|
|
670
|
+
* url: <required>, // string or URL — destination
|
|
671
|
+
* headers: {}, // request headers
|
|
672
|
+
* body: undefined, // Buffer | string | Readable | undefined
|
|
673
|
+
* timeoutMs: undefined, // wall-clock cap; no default — operator chooses
|
|
674
|
+
* idleTimeoutMs: 30000, // zero-progress cap
|
|
675
|
+
* responseMode: "buffer", // "buffer" | "stream" | "always-resolve"
|
|
676
|
+
* maxResponseBytes: undefined, // 16 MiB control / 1 GiB GET defaults; ignored in "stream"
|
|
677
|
+
* onChunk: undefined, // (chunk: Buffer) => void — fires per response chunk
|
|
678
|
+
* signal: undefined, // AbortSignal — propagated to req / stream
|
|
679
|
+
* errorClass: HttpClientError, // FrameworkError subclass for thrown errors
|
|
680
|
+
* observer: undefined, // (stage, info) => void — lifecycle hook
|
|
681
|
+
* agent: undefined, // override per-origin Agent (h1 only)
|
|
682
|
+
* preferH2: false, // attempt h2c against an HTTP origin (no ALPN)
|
|
683
|
+
* before: undefined, // array of (opts) => opts | Promise — request mutators
|
|
684
|
+
* after: undefined, // array of (response) => response | Promise — response mutators
|
|
685
|
+
* onUploadProgress: undefined, // (bytesSent, totalBytes?) => void
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* var res = await b.httpClient.request({
|
|
689
|
+
* method: "GET",
|
|
690
|
+
* url: "https://example.com/health",
|
|
691
|
+
* timeoutMs: 5000,
|
|
692
|
+
* });
|
|
693
|
+
* // → { statusCode: 200, headers: { "content-type": "application/json", ... }, body: <Buffer> }
|
|
694
|
+
*/
|
|
695
|
+
function request(opts) {
|
|
696
|
+
if (!opts || !opts.url) {
|
|
697
|
+
return Promise.reject(_makeError(opts && opts.errorClass, "BAD_ARG", "url is required", true));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Validate before/after shapes early — throw at call site if the
|
|
701
|
+
// operator passed something un-callable so the bug surfaces here
|
|
702
|
+
// rather than inside the request loop.
|
|
703
|
+
if (opts.before !== undefined) {
|
|
704
|
+
if (!Array.isArray(opts.before) || !opts.before.every(function (f) { return typeof f === "function"; })) {
|
|
705
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
706
|
+
"before must be an array of functions", true));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (opts.after !== undefined) {
|
|
710
|
+
if (!Array.isArray(opts.after) || !opts.after.every(function (f) { return typeof f === "function"; })) {
|
|
711
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
712
|
+
"after must be an array of functions", true));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (opts.onUploadProgress !== undefined && typeof opts.onUploadProgress !== "function") {
|
|
716
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
717
|
+
"onUploadProgress must be a function", true));
|
|
718
|
+
}
|
|
719
|
+
if (opts.onDownloadProgress !== undefined && typeof opts.onDownloadProgress !== "function") {
|
|
720
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
721
|
+
"onDownloadProgress must be a function", true));
|
|
722
|
+
}
|
|
723
|
+
if (opts.onChunk !== undefined && typeof opts.onChunk !== "function") {
|
|
724
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
725
|
+
"onChunk must be a function (chunk: Buffer) -> void", true));
|
|
726
|
+
}
|
|
727
|
+
if (opts.jar !== undefined && opts.jar !== null) {
|
|
728
|
+
if (typeof opts.jar !== "object" ||
|
|
729
|
+
typeof opts.jar.cookieHeaderFor !== "function" ||
|
|
730
|
+
typeof opts.jar.setFromResponse !== "function") {
|
|
731
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
732
|
+
"jar must be a b.httpClient.cookieJar.create() instance", true));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// RFC 9111 outbound HTTP cache. Validate shape at the entry-point;
|
|
736
|
+
// the cache hot path itself is drop-silent (any failure falls back
|
|
737
|
+
// to the network so caching is never a request-failure surface).
|
|
738
|
+
if (opts.cache !== undefined && opts.cache !== null) {
|
|
739
|
+
if (typeof opts.cache !== "object" ||
|
|
740
|
+
typeof opts.cache._lookup !== "function" ||
|
|
741
|
+
typeof opts.cache._evaluateStorage !== "function" ||
|
|
742
|
+
typeof opts.cache._store !== "function") {
|
|
743
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
744
|
+
"cache must be a b.httpClient.cache.create() instance", true));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// before interceptors — run in array order. Each may return a modified
|
|
749
|
+
// opts object (or return nothing to leave the running opts as-is).
|
|
750
|
+
// Caller-set defaults / observability / auth header injection lands
|
|
751
|
+
// here. Synchronous to keep the request hot path simple; async
|
|
752
|
+
// pre-flight work (e.g. token refresh) belongs in the route handler
|
|
753
|
+
// before httpClient.request is even called.
|
|
754
|
+
if (Array.isArray(opts.before) && opts.before.length > 0) {
|
|
755
|
+
var working = opts;
|
|
756
|
+
for (var bi = 0; bi < opts.before.length; bi++) {
|
|
757
|
+
var ret;
|
|
758
|
+
try { ret = opts.before[bi](working); }
|
|
759
|
+
catch (e) {
|
|
760
|
+
return Promise.reject(_makeError(opts.errorClass, "BEFORE_THREW",
|
|
761
|
+
"before[" + bi + "] threw: " + ((e && e.message) || String(e)), true));
|
|
762
|
+
}
|
|
763
|
+
if (ret && typeof ret === "object") working = ret;
|
|
764
|
+
}
|
|
765
|
+
opts = working;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Multipart shorthand: { multipart: { fields, files } } expands to
|
|
769
|
+
// body + Content-Type with the boundary parameter. Mutually exclusive
|
|
770
|
+
// with caller-supplied body / Content-Type.
|
|
771
|
+
if (opts.multipart) {
|
|
772
|
+
if (opts.body !== undefined) {
|
|
773
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
774
|
+
"request: pass either { body } or { multipart }, not both", true));
|
|
775
|
+
}
|
|
776
|
+
var built;
|
|
777
|
+
try { built = _buildMultipartBody(opts.multipart); }
|
|
778
|
+
catch (e) {
|
|
779
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG", e.message, true));
|
|
780
|
+
}
|
|
781
|
+
var mpHeaders = Object.assign({}, opts.headers || {}, {
|
|
782
|
+
"Content-Type": "multipart/form-data; boundary=" + built.boundary,
|
|
783
|
+
});
|
|
784
|
+
// Content-Length is set when the framework can statically resolve
|
|
785
|
+
// every source's byte size. Otherwise the framework omits the
|
|
786
|
+
// header and Node's HTTP layer falls back to chunked transfer —
|
|
787
|
+
// valid HTTP/1.1, requires no operator opt-in.
|
|
788
|
+
if (typeof built.contentLength === "number" && isFinite(built.contentLength)) {
|
|
789
|
+
mpHeaders["Content-Length"] = String(built.contentLength);
|
|
790
|
+
}
|
|
791
|
+
opts = Object.assign({}, opts, {
|
|
792
|
+
method: opts.method || "POST",
|
|
793
|
+
body: built.body,
|
|
794
|
+
headers: mpHeaders,
|
|
795
|
+
multipart: undefined,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// maxRedirects:
|
|
800
|
+
// undefined → today's behavior (no follow). Caller inspects 3xx.
|
|
801
|
+
// null → today's behavior (explicit). Same as undefined.
|
|
802
|
+
// 0 → no follow, but 3xx returned to caller (alias of null).
|
|
803
|
+
// N → follow up to N hops; 3xx with no Location returned as-is.
|
|
804
|
+
var maxRedirects = (opts.maxRedirects === undefined || opts.maxRedirects === null)
|
|
805
|
+
? null : opts.maxRedirects;
|
|
806
|
+
if (maxRedirects !== null) {
|
|
807
|
+
if (typeof maxRedirects !== "number" || !isFinite(maxRedirects) || maxRedirects < 0 ||
|
|
808
|
+
Math.floor(maxRedirects) !== maxRedirects) {
|
|
809
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
810
|
+
"maxRedirects must be a non-negative integer or null", true));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
var afterChain = (Array.isArray(opts.after) && opts.after.length > 0) ? opts.after : null;
|
|
814
|
+
function _runAfter(finalOpts, res) {
|
|
815
|
+
if (!afterChain) return res;
|
|
816
|
+
for (var ai = 0; ai < afterChain.length; ai++) {
|
|
817
|
+
try { afterChain[ai](finalOpts, res); }
|
|
818
|
+
catch (_e) { /* after hooks are best-effort — never break the response */ }
|
|
819
|
+
}
|
|
820
|
+
return res;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Cache layer wraps the redirect-aware path. Cache wiring is a no-op
|
|
824
|
+
// for non-GET/HEAD methods (per RFC 9111 §3) and bypassed entirely
|
|
825
|
+
// when the request opts include a body (the request mutates state on
|
|
826
|
+
// the upstream, can't be a cache hit).
|
|
827
|
+
if (opts.cache && _cacheEligibleMethod(opts.method) && opts.body == null) {
|
|
828
|
+
return _runWithCache(opts, maxRedirects, _runAfter);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (maxRedirects === null || maxRedirects === 0) {
|
|
832
|
+
return _requestSingle(opts).then(function (res) { return _runAfter(opts, res); });
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return _requestWithRedirects(opts, maxRedirects).then(function (boxed) {
|
|
836
|
+
return _runAfter(boxed.finalOpts, boxed.res);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Cache-method gate. RFC 9111 §3 — method must be GET or HEAD for the
|
|
841
|
+
// outbound cache to consider a response. Any other method shortcircuits
|
|
842
|
+
// straight to the network path (and operator code that mistakenly
|
|
843
|
+
// passed a cache instance to a POST sees the same network behaviour as
|
|
844
|
+
// without the cache, no surprise).
|
|
845
|
+
function _cacheEligibleMethod(method) {
|
|
846
|
+
var m = String(method || "GET").toUpperCase();
|
|
847
|
+
return m === "GET" || m === "HEAD";
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Wrap an outbound headers object with the framework's cache-decision
|
|
851
|
+
// markers. Mutates a copy; never the original.
|
|
852
|
+
function _withCacheHeaders(res, status, ageSeconds) {
|
|
853
|
+
var headers = Object.assign({}, res.headers || {});
|
|
854
|
+
headers["x-blamejs-cache"] = status;
|
|
855
|
+
if (typeof ageSeconds === "number" && ageSeconds >= 0) {
|
|
856
|
+
headers["age"] = String(Math.floor(ageSeconds));
|
|
857
|
+
}
|
|
858
|
+
return Object.assign({}, res, { headers: headers });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function _runWithCache(opts, maxRedirects, runAfter) {
|
|
862
|
+
var cache = opts.cache;
|
|
863
|
+
var method = String(opts.method || "GET").toUpperCase();
|
|
864
|
+
var requestHeaders = opts.headers || {};
|
|
865
|
+
var nowMs = Date.now();
|
|
866
|
+
|
|
867
|
+
// 1. Lookup. Cache lookups themselves are drop-silent; on store
|
|
868
|
+
// failure we treat the call as a miss.
|
|
869
|
+
var got = null;
|
|
870
|
+
try { got = cache._lookup(method, opts.url, requestHeaders); }
|
|
871
|
+
catch (_e) { got = null; }
|
|
872
|
+
|
|
873
|
+
function _doNetwork(extraReqHeaders) {
|
|
874
|
+
var nextOpts = opts;
|
|
875
|
+
if (extraReqHeaders) {
|
|
876
|
+
nextOpts = Object.assign({}, opts, {
|
|
877
|
+
headers: Object.assign({}, opts.headers || {}, extraReqHeaders),
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (maxRedirects === null || maxRedirects === 0) {
|
|
881
|
+
return _requestSingle(nextOpts).then(function (res) {
|
|
882
|
+
return { finalOpts: nextOpts, res: res };
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
return _requestWithRedirects(nextOpts, maxRedirects);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// 2. Miss → network → maybe store.
|
|
889
|
+
if (!got) {
|
|
890
|
+
try { cache._emit("httpclient.cache.miss", "allowed", { url: String(opts.url), method: method }); }
|
|
891
|
+
catch (_e) { /* drop-silent */ }
|
|
892
|
+
try { cache._obsEvent("httpclient.cache.miss", 1, { method: method }); }
|
|
893
|
+
catch (_e) { /* drop-silent */ }
|
|
894
|
+
return _doNetwork(null).then(function (boxed) {
|
|
895
|
+
_maybeStore(cache, method, opts.url, requestHeaders, boxed.res);
|
|
896
|
+
return runAfter(boxed.finalOpts, _withCacheHeaders(boxed.res, "MISS"));
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// 3. Hit. Decide fresh / stale / revalidate.
|
|
901
|
+
var entry = got.entry;
|
|
902
|
+
var evaluation;
|
|
903
|
+
try { evaluation = cache._evaluateStored(entry, nowMs); }
|
|
904
|
+
catch (_e) {
|
|
905
|
+
// Malformed entry — drop it, treat as miss.
|
|
906
|
+
try { cache.invalidate(method, opts.url, requestHeaders); }
|
|
907
|
+
catch (_e2) { /* drop-silent */ }
|
|
908
|
+
return _doNetwork(null).then(function (boxed) {
|
|
909
|
+
_maybeStore(cache, method, opts.url, requestHeaders, boxed.res);
|
|
910
|
+
return runAfter(boxed.finalOpts, _withCacheHeaders(boxed.res, "MISS"));
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (evaluation.fresh && !evaluation.mustRevalidate) {
|
|
915
|
+
var age = cache._serveAgeSeconds(entry, nowMs);
|
|
916
|
+
try { cache._emit("httpclient.cache.hit", "allowed", { url: String(opts.url), method: method, ageMs: evaluation.ageMs }); }
|
|
917
|
+
catch (_e) { /* drop-silent */ }
|
|
918
|
+
try { cache._obsEvent("httpclient.cache.hit", 1, { method: method }); }
|
|
919
|
+
catch (_e) { /* drop-silent */ }
|
|
920
|
+
var hitRes = {
|
|
921
|
+
statusCode: entry.statusCode,
|
|
922
|
+
headers: Object.assign({}, entry.headers),
|
|
923
|
+
body: Buffer.isBuffer(entry.body) ? Buffer.from(entry.body) : entry.body,
|
|
924
|
+
cacheStatus: "HIT",
|
|
925
|
+
};
|
|
926
|
+
return Promise.resolve(runAfter(opts, _withCacheHeaders(hitRes, "HIT", age)));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// 4. Stale or must-revalidate. Within stale-while-revalidate or
|
|
930
|
+
// defaultMaxStale grace, we serve stale + kick off background
|
|
931
|
+
// revalidation. Otherwise we revalidate inline.
|
|
932
|
+
var ageOverFresh = Math.max(0, evaluation.ageMs - evaluation.freshnessMs);
|
|
933
|
+
var swrApplies = !evaluation.mustRevalidate &&
|
|
934
|
+
ageOverFresh < Math.max(evaluation.swrWindowMs, evaluation.defaultStaleMs);
|
|
935
|
+
|
|
936
|
+
if (swrApplies && cache.revalidateInBackground) {
|
|
937
|
+
// Serve stale immediately, kick off background revalidation. We
|
|
938
|
+
// explicitly DON'T await the background revalidation Promise so
|
|
939
|
+
// the caller gets the stale response immediately. We also catch
|
|
940
|
+
// its error so an unhandled rejection doesn't escape.
|
|
941
|
+
var ageStale = cache._serveAgeSeconds(entry, nowMs);
|
|
942
|
+
try { cache._emit("httpclient.cache.stale", "allowed", { url: String(opts.url), method: method, ageMs: evaluation.ageMs, mode: "swr" }); }
|
|
943
|
+
catch (_e) { /* drop-silent */ }
|
|
944
|
+
try { cache._obsEvent("httpclient.cache.stale", 1, { method: method, mode: "swr" }); }
|
|
945
|
+
catch (_e) { /* drop-silent */ }
|
|
946
|
+
var staleRes = {
|
|
947
|
+
statusCode: entry.statusCode,
|
|
948
|
+
headers: Object.assign({}, entry.headers),
|
|
949
|
+
body: Buffer.isBuffer(entry.body) ? Buffer.from(entry.body) : entry.body,
|
|
950
|
+
cacheStatus: "STALE",
|
|
951
|
+
};
|
|
952
|
+
// Background revalidation — fire-and-forget, errors swallowed (the
|
|
953
|
+
// next caller observes the stale entry until either the upstream
|
|
954
|
+
// recovers or stale-if-error / s-w-r windows expire).
|
|
955
|
+
setImmediate(function () {
|
|
956
|
+
_revalidate(cache, method, opts, entry, requestHeaders).catch(function () {
|
|
957
|
+
/* background revalidation best-effort; swallow */
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
return Promise.resolve(runAfter(opts, _withCacheHeaders(staleRes, "STALE", ageStale)));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// 5. Inline conditional revalidation. Build If-None-Match /
|
|
964
|
+
// If-Modified-Since from the stored entry, fire the network
|
|
965
|
+
// request, branch on 304 vs anything-else.
|
|
966
|
+
return _revalidate(cache, method, opts, entry, requestHeaders).then(function (rev) {
|
|
967
|
+
if (rev.kind === "not-modified") {
|
|
968
|
+
var ageRev = cache._serveAgeSeconds(rev.refreshed || entry, Date.now());
|
|
969
|
+
var revRes = {
|
|
970
|
+
statusCode: (rev.refreshed || entry).statusCode,
|
|
971
|
+
headers: Object.assign({}, (rev.refreshed || entry).headers),
|
|
972
|
+
body: Buffer.isBuffer((rev.refreshed || entry).body)
|
|
973
|
+
? Buffer.from((rev.refreshed || entry).body)
|
|
974
|
+
: (rev.refreshed || entry).body,
|
|
975
|
+
cacheStatus: "REVALIDATED",
|
|
976
|
+
};
|
|
977
|
+
return runAfter(opts, _withCacheHeaders(revRes, "REVALIDATED", ageRev));
|
|
978
|
+
}
|
|
979
|
+
if (rev.kind === "fresh-response") {
|
|
980
|
+
_maybeStore(cache, method, opts.url, requestHeaders, rev.res);
|
|
981
|
+
return runAfter(rev.finalOpts || opts, _withCacheHeaders(rev.res, "MISS"));
|
|
982
|
+
}
|
|
983
|
+
// rev.kind === "error" — try stale-if-error.
|
|
984
|
+
var sieMs = (evaluation.sieWindowMs || 0);
|
|
985
|
+
if (sieMs > 0 && ageOverFresh < sieMs) {
|
|
986
|
+
var ageErr = cache._serveAgeSeconds(entry, Date.now());
|
|
987
|
+
try { cache._emit("httpclient.cache.stale", "allowed", { url: String(opts.url), method: method, ageMs: evaluation.ageMs, mode: "sie", error: rev.error && rev.error.message }); }
|
|
988
|
+
catch (_e) { /* drop-silent */ }
|
|
989
|
+
try { cache._obsEvent("httpclient.cache.stale", 1, { method: method, mode: "sie" }); }
|
|
990
|
+
catch (_e) { /* drop-silent */ }
|
|
991
|
+
var sieRes = {
|
|
992
|
+
statusCode: entry.statusCode,
|
|
993
|
+
headers: Object.assign({}, entry.headers),
|
|
994
|
+
body: Buffer.isBuffer(entry.body) ? Buffer.from(entry.body) : entry.body,
|
|
995
|
+
cacheStatus: "STALE",
|
|
996
|
+
};
|
|
997
|
+
return runAfter(opts, _withCacheHeaders(sieRes, "STALE", ageErr));
|
|
998
|
+
}
|
|
999
|
+
return Promise.reject(rev.error);
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Build conditional headers for revalidation per RFC 9110 §13.
|
|
1004
|
+
function _conditionalHeaders(entry) {
|
|
1005
|
+
var out = {};
|
|
1006
|
+
if (entry.etag) out["If-None-Match"] = entry.etag;
|
|
1007
|
+
if (entry.lastModified) out["If-Modified-Since"] = entry.lastModified;
|
|
1008
|
+
return out;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Run a revalidation request. Returns one of:
|
|
1012
|
+
// { kind: "not-modified", refreshed } — upstream returned 304
|
|
1013
|
+
// { kind: "fresh-response", res, finalOpts } — upstream returned 2xx/...
|
|
1014
|
+
// { kind: "error", error } — network or upstream error
|
|
1015
|
+
function _revalidate(cache, method, opts, entry, requestHeaders) {
|
|
1016
|
+
var conditional = _conditionalHeaders(entry);
|
|
1017
|
+
var nextOpts = Object.assign({}, opts, {
|
|
1018
|
+
headers: Object.assign({}, requestHeaders, conditional),
|
|
1019
|
+
// Stream-mode bypass: revalidation always uses buffer mode so the
|
|
1020
|
+
// 304 / fresh-response branches both have buffered body in hand
|
|
1021
|
+
// ready to merge / store.
|
|
1022
|
+
responseMode: "always-resolve",
|
|
1023
|
+
// Ensure we don't recurse into the cache layer on the revalidation
|
|
1024
|
+
// request itself. Pass cache as null/undefined.
|
|
1025
|
+
cache: undefined,
|
|
1026
|
+
});
|
|
1027
|
+
var maxRedirects = (opts.maxRedirects === undefined || opts.maxRedirects === null)
|
|
1028
|
+
? null : opts.maxRedirects;
|
|
1029
|
+
var p = (maxRedirects === null || maxRedirects === 0)
|
|
1030
|
+
? _requestSingle(nextOpts).then(function (res) { return { finalOpts: nextOpts, res: res }; })
|
|
1031
|
+
: _requestWithRedirects(nextOpts, maxRedirects);
|
|
1032
|
+
|
|
1033
|
+
return p.then(function (boxed) {
|
|
1034
|
+
var res = boxed.res;
|
|
1035
|
+
if (res.statusCode === 304) { // allow:raw-byte-literal — HTTP 304 Not Modified status code, not bytes
|
|
1036
|
+
// Merge 304 headers into the stored entry.
|
|
1037
|
+
var refreshed;
|
|
1038
|
+
try { refreshed = cache._refreshFrom304(entry, res.headers); }
|
|
1039
|
+
catch (_e) { refreshed = entry; }
|
|
1040
|
+
try { cache._emit("httpclient.cache.revalidated", "allowed", { url: String(opts.url), method: method }); }
|
|
1041
|
+
catch (_e) { /* drop-silent */ }
|
|
1042
|
+
try { cache._obsEvent("httpclient.cache.revalidated", 1, { method: method }); }
|
|
1043
|
+
catch (_e) { /* drop-silent */ }
|
|
1044
|
+
return { kind: "not-modified", refreshed: refreshed };
|
|
1045
|
+
}
|
|
1046
|
+
return { kind: "fresh-response", res: res, finalOpts: boxed.finalOpts };
|
|
1047
|
+
}, function (err) {
|
|
1048
|
+
return { kind: "error", error: err };
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Decide whether to store, then store. Drop-silent on any internal
|
|
1053
|
+
// throw so caching cannot surface as a request failure.
|
|
1054
|
+
function _maybeStore(cache, method, url, requestHeaders, res) {
|
|
1055
|
+
try {
|
|
1056
|
+
var evaluation = cache._evaluateStorage(method, res.statusCode, res.headers || {});
|
|
1057
|
+
if (!evaluation.cacheable) return;
|
|
1058
|
+
cache._store(method, url, requestHeaders, res.statusCode, res.headers || {}, res.body, evaluation);
|
|
1059
|
+
} catch (_e) { /* drop-silent — caching never breaks the request */ }
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function _requestWithRedirects(opts, hopsLeft) {
|
|
1063
|
+
var originalUrl = opts.url;
|
|
1064
|
+
var originalOrigin = null;
|
|
1065
|
+
try {
|
|
1066
|
+
var u0 = safeUrl.parse(opts.url, { allowedProtocols: safeUrl.ALLOW_HTTP_ALL });
|
|
1067
|
+
originalOrigin = u0.protocol + "//" + u0.host;
|
|
1068
|
+
} catch (_e) { /* request() will reject on next hop's parse */ }
|
|
1069
|
+
// onRedirect: function ({ from, to, hop, headersStripped, statusCode }) — called
|
|
1070
|
+
// BEFORE each follow. Operator can mutate the next-hop URL or abort
|
|
1071
|
+
// the redirect by throwing. Async hooks are awaited.
|
|
1072
|
+
var onRedirect = typeof opts.onRedirect === "function" ? opts.onRedirect : null;
|
|
1073
|
+
var hopCount = 0;
|
|
1074
|
+
|
|
1075
|
+
var current = Object.assign({}, opts, { _resolveOnRedirect: true });
|
|
1076
|
+
function _follow() {
|
|
1077
|
+
return _requestSingle(current).then(function (res) {
|
|
1078
|
+
if (!REDIRECT_STATUSES.has(res.statusCode) || hopsLeft <= 0) {
|
|
1079
|
+
return { finalOpts: current, res: res };
|
|
1080
|
+
}
|
|
1081
|
+
var loc = res.headers && (res.headers.location || res.headers.Location);
|
|
1082
|
+
if (!loc) return { finalOpts: current, res: res }; // 3xx with no Location — operator handles
|
|
1083
|
+
hopsLeft -= 1;
|
|
1084
|
+
hopCount += 1;
|
|
1085
|
+
|
|
1086
|
+
// Resolve relative Location against the just-fetched URL (the URL
|
|
1087
|
+
// of the request that produced the redirect, which may itself be a
|
|
1088
|
+
// post-redirect URL).
|
|
1089
|
+
var nextUrl;
|
|
1090
|
+
try {
|
|
1091
|
+
// Resolve relative Location against current URL using Node's URL
|
|
1092
|
+
// base-URL form. Re-validate the resolved absolute through safeUrl
|
|
1093
|
+
// immediately below.
|
|
1094
|
+
nextUrl = Reflect.construct(URL, [loc, current.url]).toString();
|
|
1095
|
+
}
|
|
1096
|
+
catch (_e) {
|
|
1097
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_REDIRECT",
|
|
1098
|
+
"Location header invalid URL: " + loc, true));
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Cross-origin auth-header strip.
|
|
1102
|
+
var nextHeaders = current.headers || {};
|
|
1103
|
+
var nextOrigin;
|
|
1104
|
+
try {
|
|
1105
|
+
var nu = safeUrl.parse(nextUrl, { allowedProtocols: safeUrl.ALLOW_HTTP_ALL });
|
|
1106
|
+
nextOrigin = nu.protocol + "//" + nu.host;
|
|
1107
|
+
} catch (_e) { /* request() will reject when it tries to parse */ }
|
|
1108
|
+
var headersStripped = false;
|
|
1109
|
+
if (originalOrigin && nextOrigin && nextOrigin !== originalOrigin) {
|
|
1110
|
+
nextHeaders = _stripCrossOriginAuth(nextHeaders);
|
|
1111
|
+
headersStripped = true;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// 303 → always GET; body dropped. 301/302 → historical clients
|
|
1115
|
+
// also coerce non-GET bodies (we follow that convention). 307/308
|
|
1116
|
+
// → preserve method + body.
|
|
1117
|
+
var nextMethod = current.method || "GET";
|
|
1118
|
+
var nextBody = current.body;
|
|
1119
|
+
if (res.statusCode === 303 ||
|
|
1120
|
+
((res.statusCode === 301 || res.statusCode === 302) &&
|
|
1121
|
+
nextMethod !== "GET" && nextMethod !== "HEAD")) {
|
|
1122
|
+
nextMethod = "GET";
|
|
1123
|
+
nextBody = undefined;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function _continueFollow() {
|
|
1127
|
+
current = Object.assign({}, current, {
|
|
1128
|
+
url: nextUrl,
|
|
1129
|
+
method: nextMethod,
|
|
1130
|
+
body: nextBody,
|
|
1131
|
+
headers: nextHeaders,
|
|
1132
|
+
_resolveOnRedirect: true,
|
|
1133
|
+
});
|
|
1134
|
+
return _follow();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Caller-supplied redirect hook fires here. The hook can throw
|
|
1138
|
+
// (sync) or reject (async) to abort the follow with a custom
|
|
1139
|
+
// error; otherwise we proceed to the next hop. We pre-bind the
|
|
1140
|
+
// values the hook gets and pass them in a frozen object so a
|
|
1141
|
+
// caller can't mutate the in-flight pipeline by side-effect.
|
|
1142
|
+
if (onRedirect) {
|
|
1143
|
+
var hookEvent = Object.freeze({
|
|
1144
|
+
from: current.url,
|
|
1145
|
+
to: nextUrl,
|
|
1146
|
+
hop: hopCount,
|
|
1147
|
+
statusCode: res.statusCode,
|
|
1148
|
+
headersStripped: headersStripped,
|
|
1149
|
+
method: nextMethod,
|
|
1150
|
+
});
|
|
1151
|
+
try {
|
|
1152
|
+
var hookResult = onRedirect(hookEvent);
|
|
1153
|
+
if (hookResult && typeof hookResult.then === "function") {
|
|
1154
|
+
return hookResult.then(function () { return _continueFollow(); });
|
|
1155
|
+
}
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
return Promise.reject(_makeError(opts.errorClass, "REDIRECT_ABORTED",
|
|
1158
|
+
"onRedirect hook refused redirect: " + ((e && e.message) || String(e)), true));
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return _continueFollow();
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
void originalUrl;
|
|
1165
|
+
return _follow();
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function _requestSingle(opts) {
|
|
1169
|
+
// Validate scheme + shape via url-safe. Default is HTTPS-only — the
|
|
1170
|
+
// framework refuses to silently drop bytes on the wire as cleartext.
|
|
1171
|
+
// Callers with cleartext endpoints (h2c, internal services, test
|
|
1172
|
+
// fixtures) explicitly opt in via opts.allowedProtocols
|
|
1173
|
+
// (safeUrl.ALLOW_HTTP_ALL accepts both http: and https:).
|
|
1174
|
+
var u;
|
|
1175
|
+
try {
|
|
1176
|
+
u = safeUrl.parse(opts.url, {
|
|
1177
|
+
allowedProtocols: opts.allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
1178
|
+
errorClass: opts.errorClass,
|
|
1179
|
+
});
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
return Promise.reject(e);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Optional outbound destination allowlist. When opts.allowedHosts
|
|
1185
|
+
// is set, only URLs whose hostname is on the list are permitted.
|
|
1186
|
+
// Layer above safeUrl (scheme/userinfo gate) and above ssrfGuard
|
|
1187
|
+
// (IP-class gate) — operators with strict egress policies pin the
|
|
1188
|
+
// outbound destinations the app is allowed to talk to so a
|
|
1189
|
+
// compromised process can't reach arbitrary upstreams.
|
|
1190
|
+
//
|
|
1191
|
+
// Entry forms (each entry is a string OR an object):
|
|
1192
|
+
// "api.partner.com" — exact host match
|
|
1193
|
+
// ".partner.com" — suffix match: "api.partner.com" yes,
|
|
1194
|
+
// "evilpartner.com" no
|
|
1195
|
+
// "*.partner.com" — same as ".partner.com" (DNS-glob shape
|
|
1196
|
+
// operators expect from firewall configs)
|
|
1197
|
+
// { host: "api.x.com", methods: ["GET","HEAD"] }
|
|
1198
|
+
// — method-restricted entry; methods omitted
|
|
1199
|
+
// = any method
|
|
1200
|
+
//
|
|
1201
|
+
// A disallowed call rejects with HOST_DISALLOWED AND emits an
|
|
1202
|
+
// audit event when opts.audit is wired (operator gets a structured
|
|
1203
|
+
// signal that the application tried to reach somewhere it shouldn't).
|
|
1204
|
+
if (Array.isArray(opts.allowedHosts) && opts.allowedHosts.length > 0) {
|
|
1205
|
+
var host = u.hostname.toLowerCase();
|
|
1206
|
+
var method = (opts.method || "GET").toUpperCase();
|
|
1207
|
+
var ok = false;
|
|
1208
|
+
for (var ai = 0; ai < opts.allowedHosts.length; ai++) {
|
|
1209
|
+
var entry = opts.allowedHosts[ai];
|
|
1210
|
+
var allow, allowedMethods = null;
|
|
1211
|
+
if (typeof entry === "object" && entry !== null) {
|
|
1212
|
+
allow = String(entry.host || "").toLowerCase();
|
|
1213
|
+
if (Array.isArray(entry.methods) && entry.methods.length > 0) {
|
|
1214
|
+
allowedMethods = entry.methods.map(function (m) { return String(m).toUpperCase(); });
|
|
1215
|
+
}
|
|
1216
|
+
} else {
|
|
1217
|
+
allow = String(entry || "").toLowerCase();
|
|
1218
|
+
}
|
|
1219
|
+
if (allow.length === 0) continue;
|
|
1220
|
+
// Normalise "*.x.com" to ".x.com" for the suffix match path.
|
|
1221
|
+
if (allow.charAt(0) === "*" && allow.charAt(1) === ".") allow = allow.slice(1);
|
|
1222
|
+
var matched = false;
|
|
1223
|
+
if (allow.charAt(0) === ".") {
|
|
1224
|
+
if (host === allow.slice(1) || host.endsWith(allow)) matched = true;
|
|
1225
|
+
} else if (host === allow) {
|
|
1226
|
+
matched = true;
|
|
1227
|
+
}
|
|
1228
|
+
if (!matched) continue;
|
|
1229
|
+
if (allowedMethods !== null && allowedMethods.indexOf(method) === -1) continue;
|
|
1230
|
+
ok = true;
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
if (!ok) {
|
|
1234
|
+
if (opts.audit && typeof opts.audit.safeEmit === "function") {
|
|
1235
|
+
try {
|
|
1236
|
+
opts.audit.safeEmit({
|
|
1237
|
+
action: "system.httpclient.host_denied",
|
|
1238
|
+
outcome: "denied",
|
|
1239
|
+
resource: { kind: "outbound.http", id: host },
|
|
1240
|
+
metadata: { method: method, url: opts.url, allowedHostsCount: opts.allowedHosts.length },
|
|
1241
|
+
});
|
|
1242
|
+
} catch (_e) { /* audit best-effort */ }
|
|
1243
|
+
}
|
|
1244
|
+
return Promise.reject(_makeError(opts.errorClass, "HOST_DISALLOWED",
|
|
1245
|
+
"host '" + host + "' not in allowedHosts (method=" + method + ")", true));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Attach jar-derived Cookie header BEFORE the request fires; record
|
|
1250
|
+
// Set-Cookie response headers AFTER. Both halves run when opts.jar
|
|
1251
|
+
// is set; redirect-following naturally re-runs both paths per hop
|
|
1252
|
+
// because each hop calls _requestSingle.
|
|
1253
|
+
if (opts.jar) {
|
|
1254
|
+
var headersWithJar = _attachJarCookie(opts.headers, opts.jar, opts.url);
|
|
1255
|
+
if (headersWithJar !== opts.headers) {
|
|
1256
|
+
opts = Object.assign({}, opts, { headers: headersWithJar });
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Proxy detection runs BEFORE the SSRF DNS lookup. When a proxy is
|
|
1261
|
+
// configured AND the operator explicitly opts into `allowInternal:
|
|
1262
|
+
// true`, the SSRF DNS lookup is skipped — the proxy resolves the
|
|
1263
|
+
// target hostname in its own network context (the proxy is the trust
|
|
1264
|
+
// boundary). Without this short-circuit, hostnames that only resolve
|
|
1265
|
+
// inside the proxy's network (e.g. corporate intranets, docker
|
|
1266
|
+
// service names) would fail DNS locally before the proxy ever sees
|
|
1267
|
+
// them.
|
|
1268
|
+
//
|
|
1269
|
+
// The `allowInternal: true` opt is the operator's affirmative waiver
|
|
1270
|
+
// of local SSRF defense; combined with a configured proxy, it
|
|
1271
|
+
// signals "trust the proxy's resolution + classification". When
|
|
1272
|
+
// `allowInternal` is false / array-form, the SSRF check still runs
|
|
1273
|
+
// even with a proxy — the proxy's freedom to reach internal IPs is
|
|
1274
|
+
// not a license for operator code to do so without the explicit opt-in.
|
|
1275
|
+
var proxyAgent = null;
|
|
1276
|
+
try { proxyAgent = networkProxy.agentFor(u); } catch (_e) { proxyAgent = null; }
|
|
1277
|
+
|
|
1278
|
+
var ssrfPromise;
|
|
1279
|
+
if (proxyAgent && opts.allowInternal === true) {
|
|
1280
|
+
// Proxy short-circuit — skip DNS resolution of the destination
|
|
1281
|
+
// hostname (the proxy resolves it in its own network context).
|
|
1282
|
+
// BUT still apply the textual cloud-metadata-IP block: addresses
|
|
1283
|
+
// like 169.254.169.254 (AWS / GCP / Azure / OpenStack / DO IMDS)
|
|
1284
|
+
// leak instance credentials and are NEVER overridable, even with
|
|
1285
|
+
// `allowInternal: true` AND a proxy configured. ssrfGuard's
|
|
1286
|
+
// textual check refuses metadata-IP literals at the hostname-text
|
|
1287
|
+
// layer so the proxy never receives the request.
|
|
1288
|
+
try {
|
|
1289
|
+
ssrfGuard.checkUrlTextual(u, { errorClass: opts.errorClass });
|
|
1290
|
+
} catch (eMeta) {
|
|
1291
|
+
return Promise.reject(eMeta);
|
|
1292
|
+
}
|
|
1293
|
+
ssrfPromise = Promise.resolve({ ips: null });
|
|
1294
|
+
} else {
|
|
1295
|
+
// SSRF gate — refuse private / loopback / link-local / cloud-metadata
|
|
1296
|
+
// / reserved IP destinations by default. The returned `ips` are
|
|
1297
|
+
// threaded into transport creation so the actual TCP connect pins
|
|
1298
|
+
// to those exact addresses, closing the DNS-rebinding TOCTOU window.
|
|
1299
|
+
ssrfPromise = ssrfGuard.checkUrl(u, {
|
|
1300
|
+
allowInternal: opts.allowInternal,
|
|
1301
|
+
errorClass: opts.errorClass,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return ssrfPromise.then(function (ssrfResult) {
|
|
1306
|
+
var ips = ssrfResult && ssrfResult.ips;
|
|
1307
|
+
// Caller-supplied agent bypasses transport cache (h1 only). The
|
|
1308
|
+
// operator owns the agent's connection pool — we still pass the
|
|
1309
|
+
// pinned lookup through per-request so the SSRF check's IPs win.
|
|
1310
|
+
if (opts.agent) {
|
|
1311
|
+
return _requestH1({
|
|
1312
|
+
kind: "h1",
|
|
1313
|
+
lib: u.protocol === "https:" ? https : http,
|
|
1314
|
+
agent: opts.agent,
|
|
1315
|
+
lookup: _pinnedLookupFor(ips),
|
|
1316
|
+
}, u, opts);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (proxyAgent) {
|
|
1320
|
+
return _requestH1({
|
|
1321
|
+
kind: "h1",
|
|
1322
|
+
lib: u.protocol === "https:" ? https : http,
|
|
1323
|
+
agent: proxyAgent,
|
|
1324
|
+
lookup: undefined,
|
|
1325
|
+
}, u, opts);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
return _getTransport(u, opts, ips).then(function (transport) {
|
|
1329
|
+
if (transport.kind === "h2") return _requestH2(transport, u, opts);
|
|
1330
|
+
return _requestH1(transport, u, opts);
|
|
1331
|
+
});
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// ---- _requestH1: existing node:http(s) path ----
|
|
1336
|
+
|
|
1337
|
+
function _requestH1(transport, u, opts) {
|
|
1338
|
+
return new Promise(function (resolve, reject) {
|
|
1339
|
+
var method = (opts.method || "GET").toUpperCase();
|
|
1340
|
+
var headers = Object.assign({}, opts.headers || {});
|
|
1341
|
+
var responseMode = opts.responseMode || "buffer";
|
|
1342
|
+
var maxResponseBytes = opts.maxResponseBytes ||
|
|
1343
|
+
(method === "GET" ? DEFAULT_GET_CAP : DEFAULT_CONTROL_PLANE_CAP);
|
|
1344
|
+
var observer = typeof opts.observer === "function" ? opts.observer : null;
|
|
1345
|
+
var startedAt = Date.now();
|
|
1346
|
+
|
|
1347
|
+
var signal = safeAsync.withTimeoutSignal(opts.signal || null, opts.timeoutMs);
|
|
1348
|
+
if (signal && signal.aborted) {
|
|
1349
|
+
var r0 = signal.reason;
|
|
1350
|
+
var code0 = (r0 && r0.name === "TimeoutError") ? "ETIMEDOUT" : "ABORT";
|
|
1351
|
+
reject(_makeError(opts.errorClass, code0,
|
|
1352
|
+
(r0 && r0.message) || "request aborted before start", false));
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
if (Buffer.isBuffer(opts.body)) {
|
|
1357
|
+
headers["Content-Length"] = opts.body.length;
|
|
1358
|
+
}
|
|
1359
|
+
// CVE-2026-22036 mitigation — refuse compressed responses by
|
|
1360
|
+
// default. The framework's http-client returns raw bytes capped
|
|
1361
|
+
// at maxResponseBytes; if a server sends gzip/br/zstd the cap is
|
|
1362
|
+
// on-wire bytes only, and any operator-side decompression is the
|
|
1363
|
+
// operator's responsibility to bound. Identity by default closes
|
|
1364
|
+
// the decompression-bomb amplification class. Operators who DO
|
|
1365
|
+
// want compressed responses opt in by passing an explicit
|
|
1366
|
+
// Accept-Encoding header (lowercase or canonical form).
|
|
1367
|
+
if (!headers["Accept-Encoding"] && !headers["accept-encoding"]) {
|
|
1368
|
+
headers["Accept-Encoding"] = "identity";
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
var reqOpts = {
|
|
1372
|
+
method: method,
|
|
1373
|
+
hostname: u.hostname,
|
|
1374
|
+
port: u.port || _defaultPortFor(u),
|
|
1375
|
+
path: u.pathname + (u.search || ""),
|
|
1376
|
+
headers: headers,
|
|
1377
|
+
agent: transport.agent,
|
|
1378
|
+
timeout: typeof opts.idleTimeoutMs === "number" ? opts.idleTimeoutMs : DEFAULT_IDLE_TIMEOUT_MS,
|
|
1379
|
+
};
|
|
1380
|
+
// Pin DNS to the IPs the SSRF guard validated. Closes the
|
|
1381
|
+
// rebinding TOCTOU between guard-check and actual TCP connect.
|
|
1382
|
+
if (transport.lookup) reqOpts.lookup = transport.lookup;
|
|
1383
|
+
|
|
1384
|
+
if (observer) observer("request:start", { method: method, url: String(opts.url), protocol: "h1" });
|
|
1385
|
+
|
|
1386
|
+
var settled = false;
|
|
1387
|
+
function _resolve(value) { if (!settled) { settled = true; resolve(value); } }
|
|
1388
|
+
function _reject(err) { if (!settled) { settled = true; reject(err); } }
|
|
1389
|
+
|
|
1390
|
+
var onUploadProgress = typeof opts.onUploadProgress === "function" ? opts.onUploadProgress : null;
|
|
1391
|
+
var onDownloadProgress = typeof opts.onDownloadProgress === "function" ? opts.onDownloadProgress : null;
|
|
1392
|
+
var onChunk = typeof opts.onChunk === "function" ? opts.onChunk : null;
|
|
1393
|
+
|
|
1394
|
+
var req = transport.lib.request(reqOpts, function (res) {
|
|
1395
|
+
if (observer) observer("response:headers", { statusCode: res.statusCode, headers: res.headers });
|
|
1396
|
+
|
|
1397
|
+
// Save Set-Cookie into the jar (if wired) BEFORE delivering the
|
|
1398
|
+
// response object — operator inspecting the response can already
|
|
1399
|
+
// count on the jar carrying the new state.
|
|
1400
|
+
if (opts.jar && res.headers && res.headers["set-cookie"]) {
|
|
1401
|
+
try { opts.jar.setFromResponse(opts.url, res.headers["set-cookie"]); }
|
|
1402
|
+
catch (_e) { /* jar is best-effort — never break the response */ }
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Download total: Content-Length when present, null otherwise.
|
|
1406
|
+
var dlTotal = null;
|
|
1407
|
+
if (res.headers && typeof res.headers["content-length"] === "string") {
|
|
1408
|
+
var cl = parseInt(res.headers["content-length"], 10);
|
|
1409
|
+
if (!isNaN(cl) && cl >= 0) dlTotal = cl;
|
|
1410
|
+
}
|
|
1411
|
+
var dlLoaded = 0;
|
|
1412
|
+
function _emitDownload(chunkBytes) {
|
|
1413
|
+
if (!onDownloadProgress) return;
|
|
1414
|
+
dlLoaded += chunkBytes;
|
|
1415
|
+
try { onDownloadProgress({ loaded: dlLoaded, total: dlTotal }); }
|
|
1416
|
+
catch (_e) { /* progress hooks are best-effort */ }
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (responseMode === "stream") {
|
|
1420
|
+
if (res.statusCode >= 400 && responseMode !== "always-resolve") {
|
|
1421
|
+
res.resume();
|
|
1422
|
+
return _reject(_makeError(opts.errorClass, "HTTP_ERROR",
|
|
1423
|
+
"HTTP " + res.statusCode + " " + (res.statusMessage || ""),
|
|
1424
|
+
_isPermanentStatus(res.statusCode), res.statusCode));
|
|
1425
|
+
}
|
|
1426
|
+
if (onDownloadProgress || onChunk) {
|
|
1427
|
+
// Wrap the stream so chunks emit progress + onChunk to the
|
|
1428
|
+
// operator. The framework's contract is to hand back the
|
|
1429
|
+
// response stream unmodified; fix-up via a passthrough keeps
|
|
1430
|
+
// that contract while observing the chunk sizes. onChunk
|
|
1431
|
+
// gets the buffer itself (for hash-as-you-go); a throw from
|
|
1432
|
+
// it is caught and dropped so a hash-mismatch detector can
|
|
1433
|
+
// raise without breaking the response stream — caller
|
|
1434
|
+
// surfaces the error through their own pipe handler.
|
|
1435
|
+
var passthrough = new nodeStream.PassThrough();
|
|
1436
|
+
res.on("data", function (chunk) {
|
|
1437
|
+
_emitDownload(chunk.length);
|
|
1438
|
+
if (onChunk) {
|
|
1439
|
+
try { onChunk(chunk); }
|
|
1440
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1441
|
+
}
|
|
1442
|
+
passthrough.write(chunk);
|
|
1443
|
+
});
|
|
1444
|
+
res.on("end", function () { passthrough.end(); });
|
|
1445
|
+
res.on("error", function (e) { passthrough.destroy(e); });
|
|
1446
|
+
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: passthrough });
|
|
1447
|
+
}
|
|
1448
|
+
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: res });
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
var collector = safeBuffer.boundedChunkCollector({ maxBytes: maxResponseBytes });
|
|
1452
|
+
var capExceeded = false;
|
|
1453
|
+
|
|
1454
|
+
res.on("data", function (chunk) {
|
|
1455
|
+
if (capExceeded) return;
|
|
1456
|
+
try { collector.push(chunk); }
|
|
1457
|
+
catch (_e) {
|
|
1458
|
+
capExceeded = true;
|
|
1459
|
+
req.destroy();
|
|
1460
|
+
_reject(_makeError(opts.errorClass, "RESPONSE_TOO_LARGE",
|
|
1461
|
+
"response body exceeds " + maxResponseBytes + " bytes", true));
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
_emitDownload(chunk.length);
|
|
1465
|
+
if (onChunk) {
|
|
1466
|
+
try { onChunk(chunk); }
|
|
1467
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
res.on("end", function () {
|
|
1471
|
+
if (capExceeded) return;
|
|
1472
|
+
var buf = collector.result();
|
|
1473
|
+
if (observer) observer("response:end", {
|
|
1474
|
+
statusCode: res.statusCode,
|
|
1475
|
+
durationMs: Date.now() - startedAt,
|
|
1476
|
+
bytes: buf.length,
|
|
1477
|
+
});
|
|
1478
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
1479
|
+
_resolve({ statusCode: res.statusCode, headers: res.headers, body: buf });
|
|
1480
|
+
} else if (opts._resolveOnRedirect && REDIRECT_STATUSES.has(res.statusCode)) {
|
|
1481
|
+
// Redirect-following layer needs the response object intact so
|
|
1482
|
+
// it can inspect Location and re-issue. The caller-facing
|
|
1483
|
+
// request() never sets _resolveOnRedirect — operator code that
|
|
1484
|
+
// didn't ask for redirect-following keeps seeing 3xx as errors.
|
|
1485
|
+
_resolve({ statusCode: res.statusCode, headers: res.headers, body: buf });
|
|
1486
|
+
} else if (responseMode === "always-resolve") {
|
|
1487
|
+
// Operator opted in to "give me the full response object
|
|
1488
|
+
// regardless of status." Caller branches on statusCode in
|
|
1489
|
+
// their own code path — useful for proxies / forwarders /
|
|
1490
|
+
// health-checkers / probe libraries that want to surface
|
|
1491
|
+
// the upstream response structurally instead of via an
|
|
1492
|
+
// error message string.
|
|
1493
|
+
_resolve({ statusCode: res.statusCode, headers: res.headers, body: buf });
|
|
1494
|
+
} else {
|
|
1495
|
+
var msg = "HTTP " + res.statusCode + ": " + buf.toString("utf8").slice(0, 500);
|
|
1496
|
+
_reject(_makeError(opts.errorClass, "HTTP_ERROR", msg,
|
|
1497
|
+
_isPermanentStatus(res.statusCode), res.statusCode));
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
res.on("error", function (e) {
|
|
1501
|
+
if (capExceeded) return;
|
|
1502
|
+
if (observer) observer("error", { phase: "response", message: e.message });
|
|
1503
|
+
_reject(_makeError(opts.errorClass, e.code || "RES_ERROR", e.message, false));
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
req.on("timeout", function () {
|
|
1508
|
+
req.destroy();
|
|
1509
|
+
_reject(_makeError(opts.errorClass, "ETIMEDOUT",
|
|
1510
|
+
"request idle timeout (no data for " + reqOpts.timeout + "ms)", false));
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
req.on("error", function (e) {
|
|
1514
|
+
if (observer) observer("error", { phase: "request", message: e.message });
|
|
1515
|
+
_reject(_makeError(opts.errorClass, e.code || "REQ_ERROR", e.message, false));
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
if (signal) {
|
|
1519
|
+
var onAbort = function () {
|
|
1520
|
+
var r = signal.reason;
|
|
1521
|
+
var code = (r && r.name === "TimeoutError") ? "ETIMEDOUT" : "ABORT";
|
|
1522
|
+
var msg = (r && r.message) || "request aborted";
|
|
1523
|
+
try { req.destroy(r || new Error(msg)); } catch (_e) { /* best-effort req teardown */ }
|
|
1524
|
+
_reject(_makeError(opts.errorClass, code, msg, false));
|
|
1525
|
+
};
|
|
1526
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// Upload progress: emit { loaded, total } as body bytes go to the
|
|
1530
|
+
// wire. Buffer / string bodies are sliced into chunks ourselves so
|
|
1531
|
+
// operators see incremental progress; Readable bodies emit on each
|
|
1532
|
+
// 'data' event from the source stream.
|
|
1533
|
+
var ulTotal = null;
|
|
1534
|
+
if (Buffer.isBuffer(opts.body)) ulTotal = opts.body.length;
|
|
1535
|
+
else if (typeof opts.body === "string") ulTotal = Buffer.byteLength(opts.body, "utf8");
|
|
1536
|
+
var ulLoaded = 0;
|
|
1537
|
+
function _emitUpload(chunkBytes) {
|
|
1538
|
+
if (!onUploadProgress) return;
|
|
1539
|
+
ulLoaded += chunkBytes;
|
|
1540
|
+
try { onUploadProgress({ loaded: ulLoaded, total: ulTotal }); }
|
|
1541
|
+
catch (_e) { /* progress hooks are best-effort */ }
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (opts.body && typeof opts.body.pipe === "function") {
|
|
1545
|
+
if (onUploadProgress) {
|
|
1546
|
+
opts.body.on("data", function (c) { _emitUpload(c.length); });
|
|
1547
|
+
}
|
|
1548
|
+
opts.body.on("error", function (e) {
|
|
1549
|
+
try { req.destroy(); } catch (_) { /* best-effort req teardown */ }
|
|
1550
|
+
_reject(_makeError(opts.errorClass, "REQ_BODY_ERROR",
|
|
1551
|
+
"request body stream error: " + e.message, false));
|
|
1552
|
+
});
|
|
1553
|
+
opts.body.pipe(req);
|
|
1554
|
+
} else if (Buffer.isBuffer(opts.body) || typeof opts.body === "string") {
|
|
1555
|
+
var bodyBuf = Buffer.isBuffer(opts.body) ? opts.body : Buffer.from(opts.body, "utf8");
|
|
1556
|
+
if (onUploadProgress) {
|
|
1557
|
+
// Chunked write so progress reports land before req.end().
|
|
1558
|
+
var CHUNK = C.BYTES.kib(64);
|
|
1559
|
+
var off = 0;
|
|
1560
|
+
while (off < bodyBuf.length) {
|
|
1561
|
+
var slice = bodyBuf.slice(off, Math.min(off + CHUNK, bodyBuf.length));
|
|
1562
|
+
req.write(slice);
|
|
1563
|
+
_emitUpload(slice.length);
|
|
1564
|
+
off += slice.length;
|
|
1565
|
+
}
|
|
1566
|
+
req.end();
|
|
1567
|
+
} else {
|
|
1568
|
+
req.end(bodyBuf);
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
req.end();
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// ---- _requestH2: node:http2 path ----
|
|
1577
|
+
|
|
1578
|
+
function _requestH2(transport, u, opts) {
|
|
1579
|
+
return new Promise(function (resolve, reject) {
|
|
1580
|
+
var method = (opts.method || "GET").toUpperCase();
|
|
1581
|
+
var responseMode = opts.responseMode || "buffer";
|
|
1582
|
+
var maxResponseBytes = opts.maxResponseBytes ||
|
|
1583
|
+
(method === "GET" ? DEFAULT_GET_CAP : DEFAULT_CONTROL_PLANE_CAP);
|
|
1584
|
+
var observer = typeof opts.observer === "function" ? opts.observer : null;
|
|
1585
|
+
var startedAt = Date.now();
|
|
1586
|
+
var onChunkH2 = typeof opts.onChunk === "function" ? opts.onChunk : null;
|
|
1587
|
+
|
|
1588
|
+
var signal = safeAsync.withTimeoutSignal(opts.signal || null, opts.timeoutMs);
|
|
1589
|
+
if (signal && signal.aborted) {
|
|
1590
|
+
var r0 = signal.reason;
|
|
1591
|
+
var code0 = (r0 && r0.name === "TimeoutError") ? "ETIMEDOUT" : "ABORT";
|
|
1592
|
+
reject(_makeError(opts.errorClass, code0,
|
|
1593
|
+
(r0 && r0.message) || "request aborted before start", false));
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
var headers = _toH2Headers(method, u, opts.headers || {});
|
|
1598
|
+
if (Buffer.isBuffer(opts.body)) headers["content-length"] = String(opts.body.length);
|
|
1599
|
+
|
|
1600
|
+
if (observer) observer("request:start", { method: method, url: String(opts.url), protocol: "h2" });
|
|
1601
|
+
|
|
1602
|
+
var stream;
|
|
1603
|
+
try {
|
|
1604
|
+
stream = transport.session.request(headers, {
|
|
1605
|
+
endStream: opts.body == null,
|
|
1606
|
+
});
|
|
1607
|
+
} catch (e) {
|
|
1608
|
+
reject(_makeError(opts.errorClass, e.code || "H2_REQUEST_ERROR", e.message, false));
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
var settled = false;
|
|
1613
|
+
function _resolve(v) { if (!settled) { settled = true; resolve(v); } }
|
|
1614
|
+
function _reject(e) { if (!settled) { settled = true; reject(e); } }
|
|
1615
|
+
|
|
1616
|
+
// Idle timeout for the stream itself (zero-progress detector).
|
|
1617
|
+
var idleMs = typeof opts.idleTimeoutMs === "number" ? opts.idleTimeoutMs : DEFAULT_IDLE_TIMEOUT_MS;
|
|
1618
|
+
stream.setTimeout(idleMs, function () {
|
|
1619
|
+
try { stream.close(http2.constants.NGHTTP2_CANCEL); } catch (_e) { /* best-effort h2 stream cancel */ }
|
|
1620
|
+
_reject(_makeError(opts.errorClass, "ETIMEDOUT",
|
|
1621
|
+
"h2 stream idle timeout (no data for " + idleMs + "ms)", false));
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
stream.on("response", function (resHeaders) {
|
|
1625
|
+
var statusCode = resHeaders[":status"];
|
|
1626
|
+
var responseHeaders = _fromH2Headers(resHeaders);
|
|
1627
|
+
|
|
1628
|
+
if (observer) observer("response:headers", { statusCode: statusCode, headers: responseHeaders });
|
|
1629
|
+
|
|
1630
|
+
// Save Set-Cookie to the jar (h2 set-cookie comes through as
|
|
1631
|
+
// either a single string or array, same shape as h1).
|
|
1632
|
+
if (opts.jar && responseHeaders["set-cookie"]) {
|
|
1633
|
+
try { opts.jar.setFromResponse(opts.url, responseHeaders["set-cookie"]); }
|
|
1634
|
+
catch (_e) { /* jar best-effort */ }
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (responseMode === "stream") {
|
|
1638
|
+
if (statusCode >= 400 && responseMode !== "always-resolve") {
|
|
1639
|
+
stream.resume();
|
|
1640
|
+
return _reject(_makeError(opts.errorClass, "HTTP_ERROR",
|
|
1641
|
+
"HTTP " + statusCode, _isPermanentStatus(statusCode), statusCode));
|
|
1642
|
+
}
|
|
1643
|
+
if (onChunkH2) {
|
|
1644
|
+
var passthroughH2 = new nodeStream.PassThrough();
|
|
1645
|
+
stream.on("data", function (chunk) {
|
|
1646
|
+
try { onChunkH2(chunk); }
|
|
1647
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1648
|
+
passthroughH2.write(chunk);
|
|
1649
|
+
});
|
|
1650
|
+
stream.on("end", function () { passthroughH2.end(); });
|
|
1651
|
+
stream.on("error", function (e) { passthroughH2.destroy(e); });
|
|
1652
|
+
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: passthroughH2 });
|
|
1653
|
+
}
|
|
1654
|
+
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: stream });
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
var collector = safeBuffer.boundedChunkCollector({ maxBytes: maxResponseBytes });
|
|
1658
|
+
var capExceeded = false;
|
|
1659
|
+
|
|
1660
|
+
stream.on("data", function (chunk) {
|
|
1661
|
+
if (capExceeded) return;
|
|
1662
|
+
try { collector.push(chunk); }
|
|
1663
|
+
catch (_e) {
|
|
1664
|
+
capExceeded = true;
|
|
1665
|
+
try { stream.close(http2.constants.NGHTTP2_CANCEL); } catch (_e2) { /* best-effort h2 stream cancel */ }
|
|
1666
|
+
_reject(_makeError(opts.errorClass, "RESPONSE_TOO_LARGE",
|
|
1667
|
+
"response body exceeds " + maxResponseBytes + " bytes", true));
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (onChunkH2) {
|
|
1671
|
+
try { onChunkH2(chunk); }
|
|
1672
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
stream.on("end", function () {
|
|
1676
|
+
if (capExceeded) return;
|
|
1677
|
+
var buf = collector.result();
|
|
1678
|
+
if (observer) observer("response:end", {
|
|
1679
|
+
statusCode: statusCode,
|
|
1680
|
+
durationMs: Date.now() - startedAt,
|
|
1681
|
+
bytes: buf.length,
|
|
1682
|
+
});
|
|
1683
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
1684
|
+
_resolve({ statusCode: statusCode, headers: responseHeaders, body: buf });
|
|
1685
|
+
} else if (responseMode === "always-resolve") {
|
|
1686
|
+
_resolve({ statusCode: statusCode, headers: responseHeaders, body: buf });
|
|
1687
|
+
} else {
|
|
1688
|
+
var msg = "HTTP " + statusCode + ": " + buf.toString("utf8").slice(0, 500);
|
|
1689
|
+
_reject(_makeError(opts.errorClass, "HTTP_ERROR", msg,
|
|
1690
|
+
_isPermanentStatus(statusCode), statusCode));
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
stream.on("error", function (e) {
|
|
1696
|
+
if (observer) observer("error", { phase: "stream", message: e.message });
|
|
1697
|
+
_reject(_makeError(opts.errorClass, e.code || "H2_STREAM_ERROR", e.message, false));
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
if (signal) {
|
|
1701
|
+
var onAbort = function () {
|
|
1702
|
+
var r = signal.reason;
|
|
1703
|
+
var code = (r && r.name === "TimeoutError") ? "ETIMEDOUT" : "ABORT";
|
|
1704
|
+
var msg = (r && r.message) || "request aborted";
|
|
1705
|
+
// NGHTTP2_CANCEL is the protocol-level "I gave up" signal —
|
|
1706
|
+
// cleaner than destroying the stream.
|
|
1707
|
+
try { stream.close(http2.constants.NGHTTP2_CANCEL); } catch (_e) { /* best-effort h2 stream cancel */ }
|
|
1708
|
+
_reject(_makeError(opts.errorClass, code, msg, false));
|
|
1709
|
+
};
|
|
1710
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
if (opts.body && typeof opts.body.pipe === "function") {
|
|
1714
|
+
opts.body.on("error", function (e) {
|
|
1715
|
+
try { stream.close(http2.constants.NGHTTP2_INTERNAL_ERROR); } catch (_) { /* best-effort h2 stream cancel */ }
|
|
1716
|
+
_reject(_makeError(opts.errorClass, "REQ_BODY_ERROR",
|
|
1717
|
+
"request body stream error: " + e.message, false));
|
|
1718
|
+
});
|
|
1719
|
+
opts.body.pipe(stream);
|
|
1720
|
+
} else if (Buffer.isBuffer(opts.body)) {
|
|
1721
|
+
stream.end(opts.body);
|
|
1722
|
+
} else if (typeof opts.body === "string") {
|
|
1723
|
+
stream.end(Buffer.from(opts.body, "utf8"));
|
|
1724
|
+
}
|
|
1725
|
+
// If body is null/undefined, endStream:true was set in session.request()
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// ---- Streaming primitives ----
|
|
1730
|
+
//
|
|
1731
|
+
// downloadStream — pipe a response body to a tmp file, hash-while-piping,
|
|
1732
|
+
// atomic-rename on hash match. Operators receive `{ statusCode,
|
|
1733
|
+
// bytesWritten, hash }`; on hash mismatch the tmp file is deleted and an
|
|
1734
|
+
// HttpClientError with code "httpclient/hash-mismatch" is thrown.
|
|
1735
|
+
//
|
|
1736
|
+
// uploadMultipartStream — POST a file body via multipart/form-data
|
|
1737
|
+
// without buffering. Streams from disk through the request body using
|
|
1738
|
+
// `nodeFs.createReadStream` + `node:stream/promises` pipeline.
|
|
1739
|
+
//
|
|
1740
|
+
// Both compose through `request()` (responseMode: "stream") so safeUrl,
|
|
1741
|
+
// ssrfGuard, allowedHosts, network-proxy, audit-on-host-deny, and the
|
|
1742
|
+
// per-origin transport cache apply unchanged.
|
|
1743
|
+
|
|
1744
|
+
// Algorithms exposed to operators. Defaults to PQC-first sha3-512;
|
|
1745
|
+
// callers needing legacy-peer interop with sha-256 (S3 ETag class) opt
|
|
1746
|
+
// in explicitly.
|
|
1747
|
+
var ALLOWED_DOWNLOAD_HASH_ALGS = ["sha3-512", "sha-256", "sha-512", "shake256"];
|
|
1748
|
+
var DEFAULT_DOWNLOAD_HASH_ALG = "sha3-512";
|
|
1749
|
+
var DEFAULT_DOWNLOAD_FILE_MODE = 0o600;
|
|
1750
|
+
|
|
1751
|
+
function _hcErr(code, message, statusCode) {
|
|
1752
|
+
return new HttpClientError(code, message, true, statusCode);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Throw at config-time if opts shape is malformed — operator catches the
|
|
1756
|
+
// typo here, not inside the request loop.
|
|
1757
|
+
function _validateDownloadOpts(opts) {
|
|
1758
|
+
if (!opts || typeof opts !== "object") {
|
|
1759
|
+
throw _hcErr("httpclient/bad-opts", "downloadStream: opts must be an object");
|
|
1760
|
+
}
|
|
1761
|
+
validateOpts.requireNonEmptyString(opts.url, "downloadStream: url",
|
|
1762
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1763
|
+
validateOpts.requireNonEmptyString(opts.dest, "downloadStream: dest",
|
|
1764
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1765
|
+
validateOpts.optionalNonEmptyString(opts.hash, "downloadStream: hash",
|
|
1766
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1767
|
+
if (opts.hash !== undefined && ALLOWED_DOWNLOAD_HASH_ALGS.indexOf(opts.hash) === -1) {
|
|
1768
|
+
throw _hcErr("httpclient/bad-opts",
|
|
1769
|
+
"downloadStream: hash must be one of " + ALLOWED_DOWNLOAD_HASH_ALGS.join(", ") +
|
|
1770
|
+
"; got " + JSON.stringify(opts.hash));
|
|
1771
|
+
}
|
|
1772
|
+
if (opts.expected !== undefined) {
|
|
1773
|
+
validateOpts.requireNonEmptyString(opts.expected, "downloadStream: expected",
|
|
1774
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1775
|
+
if (!safeBuffer.isHex(opts.expected)) {
|
|
1776
|
+
throw _hcErr("httpclient/bad-opts",
|
|
1777
|
+
"downloadStream: expected must be a non-empty hex digest");
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
validateOpts.optionalPositiveFinite(opts.timeoutMs, "downloadStream: timeoutMs",
|
|
1781
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1782
|
+
if (opts.maxBytes !== undefined &&
|
|
1783
|
+
(typeof opts.maxBytes !== "number" || !isFinite(opts.maxBytes) || opts.maxBytes <= 0 ||
|
|
1784
|
+
Math.floor(opts.maxBytes) !== opts.maxBytes)) {
|
|
1785
|
+
throw _hcErr("httpclient/bad-opts",
|
|
1786
|
+
"downloadStream: maxBytes must be a positive finite integer");
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function _emitAudit(opts, action, outcome, metadata) {
|
|
1791
|
+
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
1792
|
+
try {
|
|
1793
|
+
opts.audit.safeEmit({
|
|
1794
|
+
action: action,
|
|
1795
|
+
outcome: outcome,
|
|
1796
|
+
resource: { kind: "outbound.http", id: String(opts.url || "") },
|
|
1797
|
+
metadata: metadata || {},
|
|
1798
|
+
});
|
|
1799
|
+
} catch (_e) { /* audit best-effort */ }
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/**
|
|
1803
|
+
* @primitive b.httpClient.downloadStream
|
|
1804
|
+
* @signature b.httpClient.downloadStream(opts)
|
|
1805
|
+
* @since 0.1.0
|
|
1806
|
+
* @status stable
|
|
1807
|
+
* @related b.httpClient.request, b.httpClient.uploadMultipartStream, b.atomicFile.ensureDir
|
|
1808
|
+
*
|
|
1809
|
+
* Streams a remote resource to disk while hashing the bytes in flight,
|
|
1810
|
+
* then atomically renames the tmp file to `opts.dest` only after the
|
|
1811
|
+
* hash matches `opts.expected` (when supplied). Hash mismatch deletes
|
|
1812
|
+
* the tmp file and throws `httpclient/hash-mismatch`. Composes through
|
|
1813
|
+
* `request({ responseMode: "stream" })` so the SSRF gate, allowedHosts
|
|
1814
|
+
* filter, network proxy, and per-origin transport cache all apply.
|
|
1815
|
+
*
|
|
1816
|
+
* @opts
|
|
1817
|
+
* url: <required>, // string — source
|
|
1818
|
+
* dest: <required>, // absolute filesystem path — final landing
|
|
1819
|
+
* hash: "sha3-512", // "sha3-512" | "sha-256" | "sha-512" | "shake256"
|
|
1820
|
+
* expected: undefined, // hex digest; when set, verified before rename
|
|
1821
|
+
* timeoutMs: undefined, // wall-clock cap
|
|
1822
|
+
* maxBytes: undefined, // positive integer — abort past this size
|
|
1823
|
+
* audit: undefined, // audit sink with safeEmit({...})
|
|
1824
|
+
*
|
|
1825
|
+
* @example
|
|
1826
|
+
* var result = await b.httpClient.downloadStream({
|
|
1827
|
+
* url: "https://example.com/release.tar.gz",
|
|
1828
|
+
* dest: "/var/lib/blamejs/release.tar.gz",
|
|
1829
|
+
* hash: "sha3-512",
|
|
1830
|
+
* expected: "9f86d081884c7d65...d4e5",
|
|
1831
|
+
* });
|
|
1832
|
+
* // → { statusCode: 200, bytesWritten: 1048576, hash: "9f86d081884c7d65...d4e5" }
|
|
1833
|
+
*/
|
|
1834
|
+
async function downloadStream(opts) {
|
|
1835
|
+
_validateDownloadOpts(opts);
|
|
1836
|
+
var alg = opts.hash || DEFAULT_DOWNLOAD_HASH_ALG;
|
|
1837
|
+
var dest = opts.dest;
|
|
1838
|
+
var tmpPath = dest + ".tmp-" + bCrypto.generateToken(C.BYTES.bytes(8));
|
|
1839
|
+
var dir = nodePath.dirname(dest);
|
|
1840
|
+
|
|
1841
|
+
atomicFile.ensureDir(dir);
|
|
1842
|
+
|
|
1843
|
+
// Stream-mode request — body is a Readable that emits the response
|
|
1844
|
+
// chunks. The framework's onChunk path is intentionally NOT used here
|
|
1845
|
+
// because we own the destination tmp file and need precise error
|
|
1846
|
+
// ordering between hash + write-fsync + rename.
|
|
1847
|
+
var res;
|
|
1848
|
+
try {
|
|
1849
|
+
res = await request({
|
|
1850
|
+
method: "GET",
|
|
1851
|
+
url: opts.url,
|
|
1852
|
+
headers: opts.headers || {},
|
|
1853
|
+
responseMode: "stream",
|
|
1854
|
+
timeoutMs: opts.timeoutMs,
|
|
1855
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
1856
|
+
signal: opts.signal,
|
|
1857
|
+
agent: opts.agent,
|
|
1858
|
+
allowedProtocols: opts.allowedProtocols,
|
|
1859
|
+
allowedHosts: opts.allowedHosts,
|
|
1860
|
+
allowInternal: opts.allowInternal,
|
|
1861
|
+
audit: opts.audit,
|
|
1862
|
+
errorClass: HttpClientError,
|
|
1863
|
+
});
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
_emitAudit(opts, "system.httpclient.download_stream.refused", "denied", {
|
|
1866
|
+
reason: "request-failed", message: e.message, code: e.code,
|
|
1867
|
+
});
|
|
1868
|
+
throw e;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
1872
|
+
// Stream mode of request() already rejected on >=400 above, so this
|
|
1873
|
+
// branch covers 1xx/3xx surfaces that slipped through. Drain + refuse.
|
|
1874
|
+
if (res.body && typeof res.body.resume === "function") res.body.resume();
|
|
1875
|
+
_emitAudit(opts, "system.httpclient.download_stream.refused", "denied", {
|
|
1876
|
+
reason: "non-2xx", statusCode: res.statusCode,
|
|
1877
|
+
});
|
|
1878
|
+
throw _hcErr("httpclient/http-error",
|
|
1879
|
+
"downloadStream: upstream returned HTTP " + res.statusCode, res.statusCode);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
var hasher = nodeCrypto.createHash(alg);
|
|
1883
|
+
var counter = new nodeStream.Transform({
|
|
1884
|
+
transform: function (chunk, _enc, cb) {
|
|
1885
|
+
hasher.update(chunk);
|
|
1886
|
+
counter.bytesWritten += chunk.length;
|
|
1887
|
+
if (typeof opts.maxBytes === "number" && counter.bytesWritten > opts.maxBytes) {
|
|
1888
|
+
return cb(_hcErr("httpclient/response-too-large",
|
|
1889
|
+
"downloadStream: response body exceeds maxBytes " + opts.maxBytes, res.statusCode));
|
|
1890
|
+
}
|
|
1891
|
+
cb(null, chunk);
|
|
1892
|
+
},
|
|
1893
|
+
});
|
|
1894
|
+
counter.bytesWritten = 0;
|
|
1895
|
+
|
|
1896
|
+
var fileStream = nodeFs.createWriteStream(tmpPath, { mode: DEFAULT_DOWNLOAD_FILE_MODE, flags: "w" });
|
|
1897
|
+
|
|
1898
|
+
try {
|
|
1899
|
+
await streamPromises.pipeline(res.body, counter, fileStream);
|
|
1900
|
+
} catch (e) {
|
|
1901
|
+
// Pipeline failure → tmp may be partially written. Remove + audit.
|
|
1902
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_u) { /* best-effort cleanup */ }
|
|
1903
|
+
_emitAudit(opts, "system.httpclient.download_stream.refused", "denied", {
|
|
1904
|
+
reason: "pipeline-failed", message: e.message, code: e.code,
|
|
1905
|
+
});
|
|
1906
|
+
if (e && e.isHttpClientError) throw e;
|
|
1907
|
+
throw _hcErr(e.code || "httpclient/pipeline-failed",
|
|
1908
|
+
"downloadStream: pipeline failed: " + (e.message || String(e)), res.statusCode);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// fsync the file's data + close. atomicFile.fsync is best-effort
|
|
1912
|
+
// across platforms but matches the discipline of the rest of the
|
|
1913
|
+
// framework's atomic-write paths.
|
|
1914
|
+
//
|
|
1915
|
+
// CodeQL js/insecure-temporary-file: tmpPath = dest + ".tmp-" +
|
|
1916
|
+
// bCrypto.generateToken(C.BYTES.bytes(8)) (line 1802), where
|
|
1917
|
+
// bCrypto.generateToken produces 16 hex chars of CSPRNG-derived
|
|
1918
|
+
// randomness. The path lives next to operator-supplied `dest`
|
|
1919
|
+
// (downloadStream contract — never under os.tmpdir()), and the
|
|
1920
|
+
// 64-bit unpredictable suffix defeats the symlink-pre-creation
|
|
1921
|
+
// attack the rule flags. The fd is used solely for fsync; the
|
|
1922
|
+
// file's bytes were already written by the upstream pipeline.
|
|
1923
|
+
try {
|
|
1924
|
+
var fd = nodeFs.openSync(tmpPath, "r+");
|
|
1925
|
+
try { atomicFile.fsync(fd); } finally { try { nodeFs.closeSync(fd); } catch (_c) { /* best-effort fd close */ } }
|
|
1926
|
+
} catch (_fe) { /* fsync best-effort */ }
|
|
1927
|
+
|
|
1928
|
+
var actualHex = hasher.digest("hex");
|
|
1929
|
+
if (typeof opts.expected === "string" && opts.expected.length > 0) {
|
|
1930
|
+
var expected = opts.expected.toLowerCase();
|
|
1931
|
+
if (actualHex.toLowerCase() !== expected) {
|
|
1932
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_u) { /* best-effort cleanup */ }
|
|
1933
|
+
_emitAudit(opts, "system.httpclient.download_stream.refused", "denied", {
|
|
1934
|
+
reason: "hash-mismatch", alg: alg, expected: expected, actual: actualHex,
|
|
1935
|
+
statusCode: res.statusCode, bytesWritten: counter.bytesWritten,
|
|
1936
|
+
});
|
|
1937
|
+
throw _hcErr("httpclient/hash-mismatch",
|
|
1938
|
+
"downloadStream: hash mismatch (alg=" + alg + ", expected=" + expected +
|
|
1939
|
+
", actual=" + actualHex + ")", res.statusCode);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// Atomic rename + dir fsync.
|
|
1944
|
+
try {
|
|
1945
|
+
nodeFs.renameSync(tmpPath, dest);
|
|
1946
|
+
atomicFile.fsyncDir(dir);
|
|
1947
|
+
} catch (e) {
|
|
1948
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_u) { /* best-effort cleanup */ }
|
|
1949
|
+
_emitAudit(opts, "system.httpclient.download_stream.refused", "denied", {
|
|
1950
|
+
reason: "rename-failed", message: e.message,
|
|
1951
|
+
});
|
|
1952
|
+
throw _hcErr("httpclient/rename-failed",
|
|
1953
|
+
"downloadStream: rename to " + dest + " failed: " + e.message, res.statusCode);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
_emitAudit(opts, "system.httpclient.download_stream.completed", "allowed", {
|
|
1957
|
+
statusCode: res.statusCode,
|
|
1958
|
+
bytesWritten: counter.bytesWritten,
|
|
1959
|
+
alg: alg,
|
|
1960
|
+
hashVerified: typeof opts.expected === "string" && opts.expected.length > 0,
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
return {
|
|
1964
|
+
statusCode: res.statusCode,
|
|
1965
|
+
bytesWritten: counter.bytesWritten,
|
|
1966
|
+
hash: actualHex,
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// ---- uploadMultipartStream ----
|
|
1971
|
+
|
|
1972
|
+
function _validateUploadOpts(opts) {
|
|
1973
|
+
if (!opts || typeof opts !== "object") {
|
|
1974
|
+
throw _hcErr("httpclient/bad-opts", "uploadMultipartStream: opts must be an object");
|
|
1975
|
+
}
|
|
1976
|
+
validateOpts.requireNonEmptyString(opts.url, "uploadMultipartStream: url",
|
|
1977
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1978
|
+
if (!opts.file || typeof opts.file !== "object") {
|
|
1979
|
+
throw _hcErr("httpclient/bad-opts", "uploadMultipartStream: file must be an object");
|
|
1980
|
+
}
|
|
1981
|
+
validateOpts.requireNonEmptyString(opts.file.path, "uploadMultipartStream: file.path",
|
|
1982
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1983
|
+
validateOpts.requireNonEmptyString(opts.file.fieldName, "uploadMultipartStream: file.fieldName",
|
|
1984
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1985
|
+
if (opts.fields !== undefined && (typeof opts.fields !== "object" || opts.fields === null || Array.isArray(opts.fields))) {
|
|
1986
|
+
throw _hcErr("httpclient/bad-opts", "uploadMultipartStream: fields must be an object");
|
|
1987
|
+
}
|
|
1988
|
+
validateOpts.optionalPositiveFinite(opts.timeoutMs, "uploadMultipartStream: timeoutMs",
|
|
1989
|
+
HttpClientError, "httpclient/bad-opts");
|
|
1990
|
+
if (opts.maxBytes !== undefined &&
|
|
1991
|
+
(typeof opts.maxBytes !== "number" || !isFinite(opts.maxBytes) || opts.maxBytes <= 0 ||
|
|
1992
|
+
Math.floor(opts.maxBytes) !== opts.maxBytes)) {
|
|
1993
|
+
throw _hcErr("httpclient/bad-opts",
|
|
1994
|
+
"uploadMultipartStream: maxBytes must be a positive finite integer");
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* @primitive b.httpClient.uploadMultipartStream
|
|
2000
|
+
* @signature b.httpClient.uploadMultipartStream(opts)
|
|
2001
|
+
* @since 0.1.0
|
|
2002
|
+
* @status stable
|
|
2003
|
+
* @related b.httpClient.request, b.httpClient.downloadStream
|
|
2004
|
+
*
|
|
2005
|
+
* POSTs a file body via `multipart/form-data` without buffering the
|
|
2006
|
+
* file in memory. Streams from disk through the request body using
|
|
2007
|
+
* `fs.createReadStream` + `node:stream/promises` pipeline. Throws
|
|
2008
|
+
* `httpclient/missing-file` when `opts.file.path` doesn't exist or
|
|
2009
|
+
* isn't a regular file. Composes through `request()` so SSRF gating,
|
|
2010
|
+
* proxy routing, and the per-origin transport cache apply unchanged.
|
|
2011
|
+
*
|
|
2012
|
+
* @opts
|
|
2013
|
+
* url: <required>, // string — destination
|
|
2014
|
+
* file: <required>, // { path, fieldName, filename?, contentType? }
|
|
2015
|
+
* fields: undefined, // object — extra form fields { name: value, ... }
|
|
2016
|
+
* timeoutMs: undefined, // wall-clock cap
|
|
2017
|
+
* maxBytes: undefined, // positive integer — refuse files larger than this
|
|
2018
|
+
* audit: undefined, // audit sink with safeEmit({...})
|
|
2019
|
+
*
|
|
2020
|
+
* @example
|
|
2021
|
+
* var res = await b.httpClient.uploadMultipartStream({
|
|
2022
|
+
* url: "https://example.com/upload",
|
|
2023
|
+
* file: {
|
|
2024
|
+
* path: "/var/lib/blamejs/release.tar.gz",
|
|
2025
|
+
* fieldName: "artifact",
|
|
2026
|
+
* contentType: "application/gzip",
|
|
2027
|
+
* },
|
|
2028
|
+
* fields: { releaseTag: "v1.2.3" },
|
|
2029
|
+
* });
|
|
2030
|
+
* // → { statusCode: 200, headers: { ... }, body: <Buffer> }
|
|
2031
|
+
*/
|
|
2032
|
+
async function uploadMultipartStream(opts) {
|
|
2033
|
+
_validateUploadOpts(opts);
|
|
2034
|
+
|
|
2035
|
+
var filePath = opts.file.path;
|
|
2036
|
+
var st;
|
|
2037
|
+
try { st = nodeFs.statSync(filePath); }
|
|
2038
|
+
catch (e) {
|
|
2039
|
+
_emitAudit(opts, "system.httpclient.upload_stream.refused", "denied", {
|
|
2040
|
+
reason: "missing-file", path: filePath, message: e.message,
|
|
2041
|
+
});
|
|
2042
|
+
throw _hcErr("httpclient/missing-file",
|
|
2043
|
+
"uploadMultipartStream: file.path not readable: " + e.message);
|
|
2044
|
+
}
|
|
2045
|
+
if (!st.isFile()) {
|
|
2046
|
+
_emitAudit(opts, "system.httpclient.upload_stream.refused", "denied", {
|
|
2047
|
+
reason: "not-a-regular-file", path: filePath,
|
|
2048
|
+
});
|
|
2049
|
+
throw _hcErr("httpclient/missing-file",
|
|
2050
|
+
"uploadMultipartStream: file.path is not a regular file");
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
var filename = (typeof opts.file.filename === "string" && opts.file.filename.length > 0)
|
|
2054
|
+
? opts.file.filename
|
|
2055
|
+
: nodePath.basename(filePath);
|
|
2056
|
+
var contentType = (typeof opts.file.contentType === "string" && opts.file.contentType.length > 0)
|
|
2057
|
+
? opts.file.contentType
|
|
2058
|
+
: "application/octet-stream";
|
|
2059
|
+
|
|
2060
|
+
// Reuse the existing multipart shorthand by passing { filePath } —
|
|
2061
|
+
// it produces a Readable body + sets Content-Length when sizes resolve.
|
|
2062
|
+
// _buildMultipartBody is internal; we route through request()'s
|
|
2063
|
+
// multipart shorthand so the same wire path applies.
|
|
2064
|
+
var fileSpec = {
|
|
2065
|
+
field: opts.file.fieldName,
|
|
2066
|
+
filePath: filePath,
|
|
2067
|
+
filename: filename,
|
|
2068
|
+
contentType: contentType,
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
var res;
|
|
2072
|
+
try {
|
|
2073
|
+
res = await request({
|
|
2074
|
+
method: "POST",
|
|
2075
|
+
url: opts.url,
|
|
2076
|
+
headers: opts.headers || {},
|
|
2077
|
+
multipart: { fields: opts.fields || {}, files: [fileSpec], streaming: true },
|
|
2078
|
+
timeoutMs: opts.timeoutMs,
|
|
2079
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
2080
|
+
signal: opts.signal,
|
|
2081
|
+
agent: opts.agent,
|
|
2082
|
+
allowedProtocols: opts.allowedProtocols,
|
|
2083
|
+
allowedHosts: opts.allowedHosts,
|
|
2084
|
+
allowInternal: opts.allowInternal,
|
|
2085
|
+
maxResponseBytes: opts.maxResponseBytes,
|
|
2086
|
+
audit: opts.audit,
|
|
2087
|
+
errorClass: HttpClientError,
|
|
2088
|
+
});
|
|
2089
|
+
} catch (e) {
|
|
2090
|
+
_emitAudit(opts, "system.httpclient.upload_stream.refused", "denied", {
|
|
2091
|
+
reason: "request-failed", message: e.message, code: e.code,
|
|
2092
|
+
});
|
|
2093
|
+
throw e;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
_emitAudit(opts, "system.httpclient.upload_stream.completed", "allowed", {
|
|
2097
|
+
statusCode: res.statusCode,
|
|
2098
|
+
fileBytes: st.size,
|
|
2099
|
+
fieldName: opts.file.fieldName,
|
|
2100
|
+
filename: filename,
|
|
2101
|
+
});
|
|
2102
|
+
|
|
2103
|
+
return {
|
|
2104
|
+
statusCode: res.statusCode,
|
|
2105
|
+
response: res,
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// ---- Test helpers ----
|
|
2110
|
+
|
|
2111
|
+
function _resetForTest() {
|
|
2112
|
+
_transports.forEach(function (t) {
|
|
2113
|
+
if (t && t.kind === "h1" && t.agent && typeof t.agent.destroy === "function") {
|
|
2114
|
+
try { t.agent.destroy(); } catch (_e) { /* best-effort agent teardown */ }
|
|
2115
|
+
}
|
|
2116
|
+
if (t && t.kind === "h2" && t.session) {
|
|
2117
|
+
_tearDownH2Session(t.session);
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
_transports.clear();
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
function _getCachedTransportCount() {
|
|
2124
|
+
return _transports.size;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// Diagnostic — returns 'h1' | 'h2' | null for a given URL's cached transport.
|
|
2128
|
+
function _getCachedTransportKind(url) {
|
|
2129
|
+
var u = url instanceof URL ? url : safeUrl.parse(url, { allowedProtocols: safeUrl.ALLOW_HTTP_ALL });
|
|
2130
|
+
var t = _transports.get(_originKey(u));
|
|
2131
|
+
if (!t) return null;
|
|
2132
|
+
if (t.then) return "pending";
|
|
2133
|
+
return t.kind;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
module.exports = {
|
|
2137
|
+
request: request,
|
|
2138
|
+
downloadStream: downloadStream,
|
|
2139
|
+
uploadMultipartStream: uploadMultipartStream,
|
|
2140
|
+
configurePool: configurePool,
|
|
2141
|
+
DEFAULT_CONTROL_PLANE_CAP: DEFAULT_CONTROL_PLANE_CAP,
|
|
2142
|
+
DEFAULT_GET_CAP: DEFAULT_GET_CAP,
|
|
2143
|
+
DEFAULT_AGENT_OPTS: DEFAULT_AGENT_OPTS,
|
|
2144
|
+
ALLOWED_DOWNLOAD_HASH_ALGS: ALLOWED_DOWNLOAD_HASH_ALGS,
|
|
2145
|
+
_resetForTest: _resetForTest,
|
|
2146
|
+
_getCachedTransportCount: _getCachedTransportCount,
|
|
2147
|
+
_getCachedTransportKind: _getCachedTransportKind,
|
|
2148
|
+
// Test-only — exposes the SSRF-pinned DNS lookup builder so unit
|
|
2149
|
+
// tests can confirm the callback shape matches Node's documented
|
|
2150
|
+
// `lookup(hostname, options, callback)` contract.
|
|
2151
|
+
_pinnedLookupForTest: _pinnedLookupFor,
|
|
2152
|
+
};
|