@blamejs/blamejs-shop 0.5.19 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +3 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/referrals.js +10 -6
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +21 -23
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +137 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
|
@@ -55,6 +55,7 @@ var safeAsync = require("./safe-async");
|
|
|
55
55
|
var safeBuffer = require("./safe-buffer");
|
|
56
56
|
var safeUrl = require("./safe-url");
|
|
57
57
|
var ssrfGuard = require("./ssrf-guard");
|
|
58
|
+
var net = require("node:net");
|
|
58
59
|
var networkProxy = require("./network-proxy");
|
|
59
60
|
var numericBounds = require("./numeric-bounds");
|
|
60
61
|
var validateOpts = require("./validate-opts");
|
|
@@ -319,6 +320,14 @@ function _connectHttpsWithAlpn(u, ips) {
|
|
|
319
320
|
_done(_makeH1Transport(u, ips));
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
323
|
+
// A refused handshake arrives as a bare OpenSSL alert naming neither the
|
|
324
|
+
// peer nor the posture clause that caused it. Diagnose against the
|
|
325
|
+
// options this session actually used, not the shared posture.
|
|
326
|
+
networkTls().annotateOutboundFailure(err, {
|
|
327
|
+
host: u.hostname,
|
|
328
|
+
port: Number(u.port) || 443,
|
|
329
|
+
tlsOpts: connectOpts,
|
|
330
|
+
});
|
|
322
331
|
_fail(err);
|
|
323
332
|
});
|
|
324
333
|
});
|
|
@@ -514,17 +523,18 @@ function _makeError(errorClass, code, message, permanent, statusCode) {
|
|
|
514
523
|
return new Cls(code, message, permanent, statusCode);
|
|
515
524
|
}
|
|
516
525
|
|
|
517
|
-
//
|
|
518
|
-
// too
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
526
|
+
// The three 4xx codes that are NOT permanent — the request timed out, arrived
|
|
527
|
+
// too early, or arrived too often — so the caller should retry rather than give
|
|
528
|
+
// up. RFC 9110 §15.5.
|
|
529
|
+
//
|
|
530
|
+
// These were local copies, and one of them was laundered through `C.BYTES.bytes`
|
|
531
|
+
// to get a status past a detector that only knew about byte counts. Naming them
|
|
532
|
+
// where they belong removes both the copies and the reason for the disguise.
|
|
523
533
|
function _isPermanentStatus(statusCode) {
|
|
524
|
-
if (statusCode
|
|
525
|
-
return statusCode !==
|
|
526
|
-
statusCode !==
|
|
527
|
-
statusCode !==
|
|
534
|
+
if (C.HTTP.clientError(statusCode)) {
|
|
535
|
+
return statusCode !== C.HTTP.STATUS.REQUEST_TIMEOUT &&
|
|
536
|
+
statusCode !== C.HTTP.STATUS.TOO_EARLY &&
|
|
537
|
+
statusCode !== C.HTTP.STATUS.TOO_MANY_REQUESTS;
|
|
528
538
|
}
|
|
529
539
|
return false;
|
|
530
540
|
}
|
|
@@ -599,6 +609,187 @@ function _fromH2Headers(h2Headers) {
|
|
|
599
609
|
return out;
|
|
600
610
|
}
|
|
601
611
|
|
|
612
|
+
// hostAllowed — the allowedHosts membership test, extracted so anything
|
|
613
|
+
// enforcing the same pin agrees with `request()` on what an entry means
|
|
614
|
+
// rather than re-deriving suffix and method semantics.
|
|
615
|
+
//
|
|
616
|
+
// Entry forms (each entry is a string OR an object):
|
|
617
|
+
// "api.partner.com" exact host match
|
|
618
|
+
// ".partner.com" suffix match: "api.partner.com" yes, "evilpartner.com" no
|
|
619
|
+
// "*.partner.com" same as ".partner.com" (the DNS-glob shape operators
|
|
620
|
+
// expect from firewall configs)
|
|
621
|
+
// { host: "api.x.com", methods: ["GET","HEAD"] }
|
|
622
|
+
// method-restricted; methods omitted = any method
|
|
623
|
+
function hostAllowed(host, allowedHosts, method) {
|
|
624
|
+
if (!Array.isArray(allowedHosts) || allowedHosts.length === 0) return true;
|
|
625
|
+
var wanted = String(host || "").toLowerCase();
|
|
626
|
+
var verb = String(method || "GET").toUpperCase();
|
|
627
|
+
for (var ai = 0; ai < allowedHosts.length; ai++) {
|
|
628
|
+
var entry = allowedHosts[ai];
|
|
629
|
+
var allow, allowedMethods = null;
|
|
630
|
+
if (typeof entry === "object" && entry !== null) {
|
|
631
|
+
allow = String(entry.host || "").toLowerCase();
|
|
632
|
+
if (Array.isArray(entry.methods) && entry.methods.length > 0) {
|
|
633
|
+
allowedMethods = entry.methods.map(function (m) { return String(m).toUpperCase(); });
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
allow = String(entry || "").toLowerCase();
|
|
637
|
+
}
|
|
638
|
+
if (allow.length === 0) continue;
|
|
639
|
+
// Normalise "*.x.com" to ".x.com" for the suffix match path.
|
|
640
|
+
if (allow.charAt(0) === "*" && allow.charAt(1) === ".") allow = allow.slice(1);
|
|
641
|
+
var matched = false;
|
|
642
|
+
if (allow.charAt(0) === ".") {
|
|
643
|
+
if (wanted === allow.slice(1) || wanted.endsWith(allow)) matched = true;
|
|
644
|
+
} else if (wanted === allow) {
|
|
645
|
+
matched = true;
|
|
646
|
+
}
|
|
647
|
+
if (!matched) continue;
|
|
648
|
+
if (allowedMethods !== null && allowedMethods.indexOf(verb) === -1) continue;
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Does the caller's `allowInternal` waive this destination? The option is
|
|
655
|
+
// documented and implemented as `true | CIDR[]`, so reading only the boolean
|
|
656
|
+
// would silently drop the array form the moment a caller supplies their own
|
|
657
|
+
// client — a documented waiver that stops working is as much a defect as a
|
|
658
|
+
// gate that stops gating. Matched with the same cidrContains ssrfGuard uses,
|
|
659
|
+
// so an entry means the same thing on either path. A name has no address to
|
|
660
|
+
// match a CIDR against, so only the blanket form waives one.
|
|
661
|
+
function _internalWaived(host, isLiteral, allowInternal) {
|
|
662
|
+
if (allowInternal === true) return true;
|
|
663
|
+
if (!isLiteral || !Array.isArray(allowInternal)) return false;
|
|
664
|
+
for (var i = 0; i < allowInternal.length; i += 1) {
|
|
665
|
+
try { if (ssrfGuard.cidrContains(allowInternal[i], host)) return true; }
|
|
666
|
+
catch (_e) { /* a malformed entry waives nothing */ }
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* @primitive b.httpClient.pinnedClient
|
|
673
|
+
* @signature b.httpClient.pinnedClient(client, allowedHosts)
|
|
674
|
+
* @since 0.18.18
|
|
675
|
+
* @status stable
|
|
676
|
+
* @related b.httpClient.request, b.ssrfGuard.checkUrl
|
|
677
|
+
*
|
|
678
|
+
* Wrap an HTTP client so an <code>allowedHosts</code> pin is ENFORCED rather
|
|
679
|
+
* than merely advertised, and return the wrapper.
|
|
680
|
+
*
|
|
681
|
+
* The contract for a caller-supplied client is a <code>request</code> method
|
|
682
|
+
* and nothing more, so it need not know what <code>allowedHosts</code> on a
|
|
683
|
+
* request object means. Passing the pin through as a request field therefore
|
|
684
|
+
* leaves it advisory: a client that ignores the field fetches a disallowed
|
|
685
|
+
* host while the operator believes the pin is in force. This wrapper checks
|
|
686
|
+
* the destination itself — using the same membership test
|
|
687
|
+
* <code>request()</code> applies, so an entry means the same thing either way
|
|
688
|
+
* — and refuses before delegating. A destination that cannot be parsed is
|
|
689
|
+
* refused rather than passed through unchecked.
|
|
690
|
+
*
|
|
691
|
+
* Redirects are the pin's blind spot: an allowed host can answer with a
|
|
692
|
+
* redirect to a disallowed or private one, and a client that follows it
|
|
693
|
+
* internally would fetch that without ever returning here. The framework's own
|
|
694
|
+
* client re-checks every hop. The wrapper therefore asks for redirect
|
|
695
|
+
* following to be OFF on each request it forwards — <code>maxRedirects: 0</code>,
|
|
696
|
+
* <code>followRedirects: false</code> and <code>redirect: "manual"</code>,
|
|
697
|
+
* covering the spellings in common use — and a redirect then comes back as an
|
|
698
|
+
* ordinary 3xx response for the caller to act on. A client that follows
|
|
699
|
+
* redirects regardless of all three is outside what this can enforce: it must
|
|
700
|
+
* apply the pin per hop itself, exactly as it must apply anything else its own
|
|
701
|
+
* transport decides.
|
|
702
|
+
*
|
|
703
|
+
* The pin is optional; the redirect control is not. Every caller that hands a
|
|
704
|
+
* client here validated ONE url — its scheme, its host, or both — so an absent
|
|
705
|
+
* pin still returns a wrapper, or a 307 from https to http would be free to
|
|
706
|
+
* resend the credentials that were checked onto the first hop.
|
|
707
|
+
*
|
|
708
|
+
* The framework primitives that accept an injected client
|
|
709
|
+
* (<code>b.auth.oauth.create</code>, <code>b.auth.ciba.client.create</code>,
|
|
710
|
+
* <code>b.auth.saml.fetchMdq</code>) route it through here.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* var pinned = b.httpClient.pinnedClient(myClient, ["api.partner.com"]);
|
|
714
|
+
* pinned.request({ url: "https://elsewhere.example/x" });
|
|
715
|
+
* // → rejects HOST_DISALLOWED; myClient is never called
|
|
716
|
+
*/
|
|
717
|
+
function pinnedClient(client, allowedHosts) {
|
|
718
|
+
// The pin is optional; the redirect control is not. Every caller that hands
|
|
719
|
+
// this a client validated ONE url — the scheme, the host, or both — and a
|
|
720
|
+
// redirect the wrapper never sees is a hop none of that applied to. Returning
|
|
721
|
+
// the client untouched when no pin was named would leave a 307 from https to
|
|
722
|
+
// http free to resend the credentials that were checked onto the first hop.
|
|
723
|
+
var pin = (Array.isArray(allowedHosts) && allowedHosts.length > 0) ? allowedHosts : null;
|
|
724
|
+
return {
|
|
725
|
+
request: function (opts) {
|
|
726
|
+
var target = opts && opts.url;
|
|
727
|
+
// Through safeUrl so the destination is read the way the rest of the
|
|
728
|
+
// framework reads one — obfuscated host spellings collapse, credentials
|
|
729
|
+
// in the URL are refused, and a non-http scheme is refused outright.
|
|
730
|
+
// Both http schemes are permitted here because this is not the scheme
|
|
731
|
+
// policy: each caller has its own (b.auth.oauth's allowHttp, and so on)
|
|
732
|
+
// and applies it before handing the request over.
|
|
733
|
+
var parsed = null;
|
|
734
|
+
try { parsed = safeUrl.parse(String(target), { allowedProtocols: safeUrl.ALLOW_HTTP_ALL }); }
|
|
735
|
+
catch (_e) { parsed = null; }
|
|
736
|
+
if (parsed === null) {
|
|
737
|
+
return Promise.reject(_makeError(opts && opts.errorClass, "HOST_DISALLOWED",
|
|
738
|
+
"destination '" + String(target) + "' is not a usable http(s) URL", true));
|
|
739
|
+
}
|
|
740
|
+
if (pin !== null && !hostAllowed(parsed.hostname, pin, opts && opts.method)) {
|
|
741
|
+
return Promise.reject(_makeError(opts && opts.errorClass, "HOST_DISALLOWED",
|
|
742
|
+
"host '" + parsed.hostname + "' not in allowedHosts (method=" +
|
|
743
|
+
String((opts && opts.method) || "GET").toUpperCase() + ")", true));
|
|
744
|
+
}
|
|
745
|
+
// Redirect following is turned off on the way through, pin or no pin: a
|
|
746
|
+
// hop this wrapper never sees is a hop nothing checked. Set on a copy so
|
|
747
|
+
// a caller's own request object is not mutated.
|
|
748
|
+
var forwarded = Object.assign({}, opts, {
|
|
749
|
+
maxRedirects: 0,
|
|
750
|
+
followRedirects: false,
|
|
751
|
+
redirect: "manual",
|
|
752
|
+
});
|
|
753
|
+
// The SSRF gate is the last guarantee that came free while b.httpClient
|
|
754
|
+
// was the only dialer, and a supplied client makes no such promise — a
|
|
755
|
+
// mangled or attacker-influenced endpoint would otherwise reach the
|
|
756
|
+
// metadata service straight through it.
|
|
757
|
+
//
|
|
758
|
+
// What is enforceable for a transport this does not own is the TEXTUAL
|
|
759
|
+
// half: a cloud-metadata address is refused unconditionally, and an
|
|
760
|
+
// IP-literal destination in a private, loopback, link-local or reserved
|
|
761
|
+
// range is refused unless the caller waived it with `allowInternal`. The
|
|
762
|
+
// resolve-and-pin half is deliberately not attempted: a name checked
|
|
763
|
+
// here would be resolved AGAIN by the client, so the rebinding window
|
|
764
|
+
// belongs to whichever transport actually connects and only that
|
|
765
|
+
// transport can close it. This is the same reasoning b.httpClient
|
|
766
|
+
// applies when a proxy resolves on its behalf.
|
|
767
|
+
try {
|
|
768
|
+
ssrfGuard.checkUrlTextual(parsed, { errorClass: opts && opts.errorClass });
|
|
769
|
+
// canonicalizeHost strips the IPv6 brackets and folds an IPv4-mapped
|
|
770
|
+
// address to its dotted form, so a non-canonical spelling classifies
|
|
771
|
+
// the same as the plain one.
|
|
772
|
+
var literal = ssrfGuard.canonicalizeHost(String(parsed.hostname));
|
|
773
|
+
var isLiteral = net.isIP(literal) !== 0;
|
|
774
|
+
// A loopback NAME is a loopback destination. Checking only IP literals
|
|
775
|
+
// let `localhost` — and anything under it, since the whole TLD resolves
|
|
776
|
+
// there — past a gate that refuses 127.0.0.1, which is the same
|
|
777
|
+
// destination spelled differently.
|
|
778
|
+
var internal = isLiteral
|
|
779
|
+
? (ssrfGuard.isPrivate(literal) || ssrfGuard.isLoopback(literal) ||
|
|
780
|
+
ssrfGuard.isLinkLocal(literal) || ssrfGuard.isReserved(literal))
|
|
781
|
+
: ssrfGuard.isLoopbackHost(literal);
|
|
782
|
+
if (internal && !_internalWaived(literal, isLiteral, opts && opts.allowInternal)) {
|
|
783
|
+
return Promise.reject(_makeError(opts && opts.errorClass, "HOST_DISALLOWED",
|
|
784
|
+
"destination '" + literal + "' is an internal address — pass " +
|
|
785
|
+
"allowInternal to waive this deliberately", true));
|
|
786
|
+
}
|
|
787
|
+
} catch (e) { return Promise.reject(e); }
|
|
788
|
+
return client.request(forwarded);
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
602
793
|
// ---- request() ----
|
|
603
794
|
|
|
604
795
|
var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
@@ -1376,7 +1567,7 @@ function _revalidate(cache, method, opts, entry, requestHeaders) {
|
|
|
1376
1567
|
|
|
1377
1568
|
return p.then(function (boxed) {
|
|
1378
1569
|
var res = boxed.res;
|
|
1379
|
-
if (res.statusCode ===
|
|
1570
|
+
if (res.statusCode === C.HTTP.STATUS.NOT_MODIFIED) { // HTTP 304 Not Modified status code, not bytes
|
|
1380
1571
|
// Merge 304 headers into the stored entry.
|
|
1381
1572
|
var refreshed;
|
|
1382
1573
|
try { refreshed = cache._refreshFrom304(entry, res.headers); }
|
|
@@ -1464,8 +1655,8 @@ function _requestWithRedirects(opts, hopsLeft) {
|
|
|
1464
1655
|
// → preserve method + body.
|
|
1465
1656
|
var nextMethod = current.method || "GET";
|
|
1466
1657
|
var nextBody = current.body;
|
|
1467
|
-
if (res.statusCode ===
|
|
1468
|
-
((res.statusCode ===
|
|
1658
|
+
if (res.statusCode === C.HTTP.STATUS.SEE_OTHER ||
|
|
1659
|
+
((res.statusCode === C.HTTP.STATUS.MOVED_PERMANENTLY || res.statusCode === C.HTTP.STATUS.FOUND) &&
|
|
1469
1660
|
nextMethod !== "GET" && nextMethod !== "HEAD")) {
|
|
1470
1661
|
nextMethod = "GET";
|
|
1471
1662
|
nextBody = undefined;
|
|
@@ -1559,32 +1750,7 @@ function _requestSingle(opts) {
|
|
|
1559
1750
|
if (Array.isArray(opts.allowedHosts) && opts.allowedHosts.length > 0) {
|
|
1560
1751
|
var host = u.hostname.toLowerCase();
|
|
1561
1752
|
var method = (opts.method || "GET").toUpperCase();
|
|
1562
|
-
var ok =
|
|
1563
|
-
for (var ai = 0; ai < opts.allowedHosts.length; ai++) {
|
|
1564
|
-
var entry = opts.allowedHosts[ai];
|
|
1565
|
-
var allow, allowedMethods = null;
|
|
1566
|
-
if (typeof entry === "object" && entry !== null) {
|
|
1567
|
-
allow = String(entry.host || "").toLowerCase();
|
|
1568
|
-
if (Array.isArray(entry.methods) && entry.methods.length > 0) {
|
|
1569
|
-
allowedMethods = entry.methods.map(function (m) { return String(m).toUpperCase(); });
|
|
1570
|
-
}
|
|
1571
|
-
} else {
|
|
1572
|
-
allow = String(entry || "").toLowerCase();
|
|
1573
|
-
}
|
|
1574
|
-
if (allow.length === 0) continue;
|
|
1575
|
-
// Normalise "*.x.com" to ".x.com" for the suffix match path.
|
|
1576
|
-
if (allow.charAt(0) === "*" && allow.charAt(1) === ".") allow = allow.slice(1);
|
|
1577
|
-
var matched = false;
|
|
1578
|
-
if (allow.charAt(0) === ".") {
|
|
1579
|
-
if (host === allow.slice(1) || host.endsWith(allow)) matched = true;
|
|
1580
|
-
} else if (host === allow) {
|
|
1581
|
-
matched = true;
|
|
1582
|
-
}
|
|
1583
|
-
if (!matched) continue;
|
|
1584
|
-
if (allowedMethods !== null && allowedMethods.indexOf(method) === -1) continue;
|
|
1585
|
-
ok = true;
|
|
1586
|
-
break;
|
|
1587
|
-
}
|
|
1753
|
+
var ok = hostAllowed(host, opts.allowedHosts, method);
|
|
1588
1754
|
if (!ok) {
|
|
1589
1755
|
if (opts.audit && typeof opts.audit.safeEmit === "function") {
|
|
1590
1756
|
try {
|
|
@@ -1865,6 +2031,16 @@ function _requestH1(transport, u, opts) {
|
|
|
1865
2031
|
});
|
|
1866
2032
|
|
|
1867
2033
|
req.on("error", function (e) {
|
|
2034
|
+
// The agent carrying this request owns its TLS settings, and it may be
|
|
2035
|
+
// one the caller supplied via opts.agent rather than the framework's.
|
|
2036
|
+
// Naming them explicitly means an agent that pins neither is diagnosed
|
|
2037
|
+
// as pinning neither, instead of being blamed on the shared posture.
|
|
2038
|
+
var agentTls = (reqOpts.agent && reqOpts.agent.options) || {};
|
|
2039
|
+
networkTls().annotateOutboundFailure(e, {
|
|
2040
|
+
host: reqOpts.hostname,
|
|
2041
|
+
port: Number(reqOpts.port),
|
|
2042
|
+
tlsOpts: agentTls,
|
|
2043
|
+
});
|
|
1868
2044
|
if (observer) observer("error", { phase: "request", message: e.message });
|
|
1869
2045
|
_reject(_makeError(opts.errorClass, e.code || "REQ_ERROR", e.message, false));
|
|
1870
2046
|
});
|
|
@@ -2054,7 +2230,7 @@ function _requestH2(transport, u, opts) {
|
|
|
2054
2230
|
durationMs: Date.now() - startedAt,
|
|
2055
2231
|
bytes: buf.length,
|
|
2056
2232
|
});
|
|
2057
|
-
if (statusCode
|
|
2233
|
+
if (C.HTTP.success(statusCode)) {
|
|
2058
2234
|
_resolve({ statusCode: statusCode, headers: responseHeaders, body: buf });
|
|
2059
2235
|
} else if (responseMode === "always-resolve") {
|
|
2060
2236
|
_resolve({ statusCode: statusCode, headers: responseHeaders, body: buf });
|
|
@@ -2525,6 +2701,7 @@ function _getCachedTransportKind(url) {
|
|
|
2525
2701
|
|
|
2526
2702
|
module.exports = {
|
|
2527
2703
|
request: request,
|
|
2704
|
+
pinnedClient: pinnedClient,
|
|
2528
2705
|
downloadStream: downloadStream,
|
|
2529
2706
|
uploadMultipartStream: uploadMultipartStream,
|
|
2530
2707
|
configurePool: configurePool,
|
|
@@ -676,7 +676,7 @@ async function fetchAndVerifyMark(opts) {
|
|
|
676
676
|
throw new MailBimiError("bimi/vmc-fetch-failed",
|
|
677
677
|
"bimi.fetchAndVerifyMark: GET " + url + " failed: " + ((e && e.message) || String(e)));
|
|
678
678
|
}
|
|
679
|
-
if (rsp.statusCode !==
|
|
679
|
+
if (rsp.statusCode !== C.HTTP.STATUS.OK) {
|
|
680
680
|
_emitAudit(opts, "mail.bimi.vmc.fetched", "failure",
|
|
681
681
|
{ url: url, domain: opts.domain, status: rsp.statusCode });
|
|
682
682
|
throw new MailBimiError("bimi/vmc-fetch-failed",
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
* - CVE-2019-13050 (PGP keyserver flood — not in scope here; out-of-
|
|
115
115
|
* band fingerprint pinning is the operator's responsibility)
|
|
116
116
|
*/
|
|
117
|
+
var C = require("./constants");
|
|
117
118
|
var lazyRequire = require("./lazy-require");
|
|
118
119
|
var safeBuffer = require("./safe-buffer");
|
|
119
120
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
@@ -1217,7 +1218,7 @@ function wkdFetch(email, opts) {
|
|
|
1217
1218
|
var maxBytes = typeof opts.maxKeyBytes === "number" ? opts.maxKeyBytes : (256 * 1024); // allow:raw-byte-literal — 256 KiB default key cap
|
|
1218
1219
|
var urls = wkdComputeUrl(email, { advancedHost: opts.advancedHost });
|
|
1219
1220
|
return Promise.resolve(opts.httpsGet(urls.direct)).then(function (resp) {
|
|
1220
|
-
if (resp && resp.status ===
|
|
1221
|
+
if (resp && resp.status === C.HTTP.STATUS.OK && Buffer.isBuffer(resp.body) && resp.body.length > 0) { // HTTP 200
|
|
1221
1222
|
if (safeBuffer.byteLengthOf(resp.body) > maxBytes) {
|
|
1222
1223
|
throw new MailCryptoError("mail-crypto/pgp/wkd-too-large",
|
|
1223
1224
|
"wkd.fetch: key bytes " + resp.body.length + " exceed maxKeyBytes=" + maxBytes);
|
|
@@ -1225,7 +1226,7 @@ function wkdFetch(email, opts) {
|
|
|
1225
1226
|
return { keyBytes: resp.body, source: "direct", url: urls.direct };
|
|
1226
1227
|
}
|
|
1227
1228
|
return Promise.resolve(opts.httpsGet(urls.advanced)).then(function (resp2) {
|
|
1228
|
-
if (resp2 && resp2.status ===
|
|
1229
|
+
if (resp2 && resp2.status === C.HTTP.STATUS.OK && Buffer.isBuffer(resp2.body) && resp2.body.length > 0) { // HTTP 200
|
|
1229
1230
|
if (safeBuffer.byteLengthOf(resp2.body) > maxBytes) {
|
|
1230
1231
|
throw new MailCryptoError("mail-crypto/pgp/wkd-too-large",
|
|
1231
1232
|
"wkd.fetch: key bytes " + resp2.body.length + " exceed maxKeyBytes=" + maxBytes);
|
|
@@ -582,7 +582,7 @@ function create(opts) {
|
|
|
582
582
|
responses.push({
|
|
583
583
|
href: "/caldav/" + principalId + "/",
|
|
584
584
|
propstat: [{
|
|
585
|
-
status:
|
|
585
|
+
status: C.HTTP.STATUS.OK,
|
|
586
586
|
propXml: _renderProps({
|
|
587
587
|
displayName: principalId,
|
|
588
588
|
resourcetype: "<D:collection/>",
|
|
@@ -593,7 +593,7 @@ function create(opts) {
|
|
|
593
593
|
var cals = await calStorage.listCalendars(principalId);
|
|
594
594
|
responses.push({
|
|
595
595
|
href: "/caldav/" + principalId + "/",
|
|
596
|
-
propstat: [{ status:
|
|
596
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
597
597
|
propXml: _renderProps({ displayName: principalId,
|
|
598
598
|
resourcetype: "<D:collection/>" }, body.props) }],
|
|
599
599
|
});
|
|
@@ -601,7 +601,7 @@ function create(opts) {
|
|
|
601
601
|
var cal = cals[i];
|
|
602
602
|
responses.push({
|
|
603
603
|
href: "/caldav/" + principalId + "/" + cal.id + "/",
|
|
604
|
-
propstat: [{ status:
|
|
604
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
605
605
|
propXml: _renderProps({
|
|
606
606
|
displayName: cal.displayName || cal.id,
|
|
607
607
|
resourcetype: "<D:collection/><C:calendar/>",
|
|
@@ -615,7 +615,7 @@ function create(opts) {
|
|
|
615
615
|
var components = await calStorage.listComponents(principalId, calendarId, null);
|
|
616
616
|
responses.push({
|
|
617
617
|
href: "/caldav/" + principalId + "/" + calendarId + "/",
|
|
618
|
-
propstat: [{ status:
|
|
618
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
619
619
|
propXml: _renderProps({ displayName: calendarId,
|
|
620
620
|
resourcetype: "<D:collection/><C:calendar/>" }, body.props) }],
|
|
621
621
|
});
|
|
@@ -624,7 +624,7 @@ function create(opts) {
|
|
|
624
624
|
var c = components[j];
|
|
625
625
|
responses.push({
|
|
626
626
|
href: "/caldav/" + principalId + "/" + calendarId + "/" + c.id,
|
|
627
|
-
propstat: [{ status:
|
|
627
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
628
628
|
propXml: _renderProps({
|
|
629
629
|
displayName: c.id,
|
|
630
630
|
resourcetype: "",
|
|
@@ -640,11 +640,11 @@ function create(opts) {
|
|
|
640
640
|
// Single component.
|
|
641
641
|
var comp = await calStorage.getComponent(principalId, calendarId, componentId);
|
|
642
642
|
if (!comp) {
|
|
643
|
-
responses.push({ href: req.url, status:
|
|
643
|
+
responses.push({ href: req.url, status: C.HTTP.STATUS.NOT_FOUND });
|
|
644
644
|
} else {
|
|
645
645
|
responses.push({
|
|
646
646
|
href: req.url,
|
|
647
|
-
propstat: [{ status:
|
|
647
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
648
648
|
propXml: _renderProps({
|
|
649
649
|
displayName: comp.id,
|
|
650
650
|
etag: comp.etag,
|
|
@@ -673,18 +673,18 @@ function create(opts) {
|
|
|
673
673
|
var href = report.hrefs[i];
|
|
674
674
|
var hrefParsed = _parsePath(href);
|
|
675
675
|
if (hrefParsed.rejected || hrefParsed.principalId !== principalId) {
|
|
676
|
-
responses.push({ href: href, status:
|
|
676
|
+
responses.push({ href: href, status: C.HTTP.STATUS.FORBIDDEN });
|
|
677
677
|
continue;
|
|
678
678
|
}
|
|
679
679
|
var hrefCalId = hrefParsed.parts[0];
|
|
680
680
|
var hrefCompId = hrefParsed.parts[1];
|
|
681
681
|
var comp = await calStorage.getComponent(principalId, hrefCalId, hrefCompId);
|
|
682
682
|
if (!comp) {
|
|
683
|
-
responses.push({ href: href, status:
|
|
683
|
+
responses.push({ href: href, status: C.HTTP.STATUS.NOT_FOUND });
|
|
684
684
|
} else {
|
|
685
685
|
responses.push({
|
|
686
686
|
href: href,
|
|
687
|
-
propstat: [{ status:
|
|
687
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
688
688
|
propXml: _renderProps({
|
|
689
689
|
etag: comp.etag,
|
|
690
690
|
contentType: "text/calendar; charset=utf-8",
|
|
@@ -700,7 +700,7 @@ function create(opts) {
|
|
|
700
700
|
var r = rows[j];
|
|
701
701
|
responses.push({
|
|
702
702
|
href: "/caldav/" + principalId + "/" + calendarId + "/" + r.id,
|
|
703
|
-
propstat: [{ status:
|
|
703
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
704
704
|
propXml: _renderProps({
|
|
705
705
|
etag: r.etag,
|
|
706
706
|
contentType: "text/calendar; charset=utf-8",
|
|
@@ -761,7 +761,7 @@ function create(opts) {
|
|
|
761
761
|
result = await calStorage.putComponent(principalId, calendarId, componentId,
|
|
762
762
|
bodyBuf, { ifMatch: ifMatch, ifNoneMatch: ifNoneMatch });
|
|
763
763
|
} catch (e) {
|
|
764
|
-
if (e && (e.code === "etag-mismatch" || e.statusCode ===
|
|
764
|
+
if (e && (e.code === "etag-mismatch" || e.statusCode === C.HTTP.STATUS.PRECONDITION_FAILED)) {
|
|
765
765
|
return _refuseStatus(res, 412, "ETag precondition failed");
|
|
766
766
|
}
|
|
767
767
|
throw e;
|
|
@@ -784,7 +784,7 @@ function create(opts) {
|
|
|
784
784
|
{ ifMatch: ifMatch });
|
|
785
785
|
if (!r || r.notFound) return _refuseStatus(res, 404, "Component not found");
|
|
786
786
|
} catch (e) {
|
|
787
|
-
if (e && (e.code === "etag-mismatch" || e.statusCode ===
|
|
787
|
+
if (e && (e.code === "etag-mismatch" || e.statusCode === C.HTTP.STATUS.PRECONDITION_FAILED)) {
|
|
788
788
|
return _refuseStatus(res, 412, "ETag precondition failed");
|
|
789
789
|
}
|
|
790
790
|
throw e;
|
|
@@ -922,7 +922,7 @@ function create(opts) {
|
|
|
922
922
|
if (depth === "0") {
|
|
923
923
|
responses.push({
|
|
924
924
|
href: "/carddav/" + principalId + "/",
|
|
925
|
-
propstat: [{ status:
|
|
925
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
926
926
|
propXml: _renderProps({ displayName: principalId,
|
|
927
927
|
resourcetype: "<D:collection/>" }, body.props) }],
|
|
928
928
|
});
|
|
@@ -930,7 +930,7 @@ function create(opts) {
|
|
|
930
930
|
var books = await cardStorage.listAddressbooks(principalId);
|
|
931
931
|
responses.push({
|
|
932
932
|
href: "/carddav/" + principalId + "/",
|
|
933
|
-
propstat: [{ status:
|
|
933
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
934
934
|
propXml: _renderProps({ displayName: principalId,
|
|
935
935
|
resourcetype: "<D:collection/>" }, body.props) }],
|
|
936
936
|
});
|
|
@@ -938,7 +938,7 @@ function create(opts) {
|
|
|
938
938
|
var bk = books[i];
|
|
939
939
|
responses.push({
|
|
940
940
|
href: "/carddav/" + principalId + "/" + bk.id + "/",
|
|
941
|
-
propstat: [{ status:
|
|
941
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
942
942
|
propXml: _renderProps({
|
|
943
943
|
displayName: bk.displayName || bk.id,
|
|
944
944
|
resourcetype: "<D:collection/><A:addressbook/>",
|
|
@@ -951,7 +951,7 @@ function create(opts) {
|
|
|
951
951
|
var cards = await cardStorage.listCards(principalId, addressbookId, null);
|
|
952
952
|
responses.push({
|
|
953
953
|
href: "/carddav/" + principalId + "/" + addressbookId + "/",
|
|
954
|
-
propstat: [{ status:
|
|
954
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
955
955
|
propXml: _renderProps({ displayName: addressbookId,
|
|
956
956
|
resourcetype: "<D:collection/><A:addressbook/>" }, body.props) }],
|
|
957
957
|
});
|
|
@@ -960,7 +960,7 @@ function create(opts) {
|
|
|
960
960
|
var card = cards[j];
|
|
961
961
|
responses.push({
|
|
962
962
|
href: "/carddav/" + principalId + "/" + addressbookId + "/" + card.id,
|
|
963
|
-
propstat: [{ status:
|
|
963
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
964
964
|
propXml: _renderProps({
|
|
965
965
|
displayName: card.id,
|
|
966
966
|
etag: card.etag,
|
|
@@ -974,11 +974,11 @@ function create(opts) {
|
|
|
974
974
|
} else {
|
|
975
975
|
var single = await cardStorage.getCard(principalId, addressbookId, cardId);
|
|
976
976
|
if (!single) {
|
|
977
|
-
responses.push({ href: req.url, status:
|
|
977
|
+
responses.push({ href: req.url, status: C.HTTP.STATUS.NOT_FOUND });
|
|
978
978
|
} else {
|
|
979
979
|
responses.push({
|
|
980
980
|
href: req.url,
|
|
981
|
-
propstat: [{ status:
|
|
981
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
982
982
|
propXml: _renderProps({
|
|
983
983
|
displayName: single.id,
|
|
984
984
|
etag: single.etag,
|
|
@@ -1008,18 +1008,18 @@ function create(opts) {
|
|
|
1008
1008
|
var href = report.hrefs[i];
|
|
1009
1009
|
var hp = _parsePath(href);
|
|
1010
1010
|
if (hp.rejected || hp.principalId !== principalId) {
|
|
1011
|
-
responses.push({ href: href, status:
|
|
1011
|
+
responses.push({ href: href, status: C.HTTP.STATUS.FORBIDDEN });
|
|
1012
1012
|
continue;
|
|
1013
1013
|
}
|
|
1014
1014
|
var hpBookId = hp.parts[0];
|
|
1015
1015
|
var hpCardId = hp.parts[1];
|
|
1016
1016
|
var card = await cardStorage.getCard(principalId, hpBookId, hpCardId);
|
|
1017
1017
|
if (!card) {
|
|
1018
|
-
responses.push({ href: href, status:
|
|
1018
|
+
responses.push({ href: href, status: C.HTTP.STATUS.NOT_FOUND });
|
|
1019
1019
|
} else {
|
|
1020
1020
|
responses.push({
|
|
1021
1021
|
href: href,
|
|
1022
|
-
propstat: [{ status:
|
|
1022
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
1023
1023
|
propXml: _renderProps({
|
|
1024
1024
|
etag: card.etag,
|
|
1025
1025
|
contentType: "text/vcard; charset=utf-8",
|
|
@@ -1035,7 +1035,7 @@ function create(opts) {
|
|
|
1035
1035
|
var r = rows[j];
|
|
1036
1036
|
responses.push({
|
|
1037
1037
|
href: "/carddav/" + principalId + "/" + addressbookId + "/" + r.id,
|
|
1038
|
-
propstat: [{ status:
|
|
1038
|
+
propstat: [{ status: C.HTTP.STATUS.OK,
|
|
1039
1039
|
propXml: _renderProps({
|
|
1040
1040
|
etag: r.etag,
|
|
1041
1041
|
contentType: "text/vcard; charset=utf-8",
|
|
@@ -1090,7 +1090,7 @@ function create(opts) {
|
|
|
1090
1090
|
result = await cardStorage.putCard(principalId, addressbookId, cardId,
|
|
1091
1091
|
bodyBuf, { ifMatch: ifMatch, ifNoneMatch: ifNoneMatch });
|
|
1092
1092
|
} catch (e) {
|
|
1093
|
-
if (e && (e.code === "etag-mismatch" || e.statusCode ===
|
|
1093
|
+
if (e && (e.code === "etag-mismatch" || e.statusCode === C.HTTP.STATUS.PRECONDITION_FAILED)) {
|
|
1094
1094
|
return _refuseStatus(res, 412, "ETag precondition failed");
|
|
1095
1095
|
}
|
|
1096
1096
|
throw e;
|
|
@@ -1112,7 +1112,7 @@ function create(opts) {
|
|
|
1112
1112
|
{ ifMatch: ifMatch });
|
|
1113
1113
|
if (!r || r.notFound) return _refuseStatus(res, 404, "Card not found");
|
|
1114
1114
|
} catch (e) {
|
|
1115
|
-
if (e && (e.code === "etag-mismatch" || e.statusCode ===
|
|
1115
|
+
if (e && (e.code === "etag-mismatch" || e.statusCode === C.HTTP.STATUS.PRECONDITION_FAILED)) {
|
|
1116
1116
|
return _refuseStatus(res, 412, "ETag precondition failed");
|
|
1117
1117
|
}
|
|
1118
1118
|
throw e;
|
|
@@ -949,7 +949,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
949
949
|
|
|
950
950
|
return function tlsRptHandler(req, res) {
|
|
951
951
|
if (req.method !== "POST") {
|
|
952
|
-
res.writeHead(
|
|
952
|
+
res.writeHead(C.HTTP.STATUS.METHOD_NOT_ALLOWED, { "Allow": "POST", "Content-Type": "text/plain" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
953
953
|
res.end("RFC 8460 §5.4 requires POST\n");
|
|
954
954
|
return;
|
|
955
955
|
}
|
|
@@ -961,7 +961,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
961
961
|
});
|
|
962
962
|
if (onRefuse) try { onRefuse("mail-tlsrpt/bad-content-type", "unexpected content-type " + ctRoot, req); }
|
|
963
963
|
catch (_e) { /* drop-silent */ }
|
|
964
|
-
res.writeHead(
|
|
964
|
+
res.writeHead(C.HTTP.STATUS.UNSUPPORTED_MEDIA_TYPE, { "Content-Type": "text/plain", "Accept": "application/tlsrpt+json, application/tlsrpt+gzip" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
965
965
|
res.end("RFC 8460 §6.4-6.5 media types required\n");
|
|
966
966
|
return;
|
|
967
967
|
}
|
|
@@ -978,7 +978,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
978
978
|
_safeAuditEmit(opts.audit, "mail.tlsrpt.ingest_http", "denied", { reason: "unauthenticated" });
|
|
979
979
|
if (onRefuse) try { onRefuse("mail-tlsrpt/unauthenticated", "authenticate(req) returned falsy", req); }
|
|
980
980
|
catch (_e) { /* drop-silent */ }
|
|
981
|
-
res.writeHead(
|
|
981
|
+
res.writeHead(C.HTTP.STATUS.UNAUTHORIZED, { "Content-Type": "text/plain", "Error-Type": "mail-tlsrpt/unauthenticated" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
982
982
|
res.end("authentication required\n");
|
|
983
983
|
return;
|
|
984
984
|
}
|
|
@@ -989,7 +989,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
989
989
|
});
|
|
990
990
|
if (onRefuse) try { onRefuse("mail-tlsrpt/auth-error", (err && err.message) || String(err), req); }
|
|
991
991
|
catch (_e) { /* drop-silent */ }
|
|
992
|
-
res.writeHead(
|
|
992
|
+
res.writeHead(C.HTTP.STATUS.INTERNAL_SERVER_ERROR, { "Content-Type": "text/plain", "Error-Type": "mail-tlsrpt/auth-error" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
993
993
|
res.end("authenticate hook threw\n");
|
|
994
994
|
});
|
|
995
995
|
return;
|
|
@@ -1015,7 +1015,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
1015
1015
|
if (onRefuse) try { onRefuse("mail-tlsrpt/oversize-compressed", "body exceeded " + maxCompressed + " bytes", req); }
|
|
1016
1016
|
catch (_e) { /* drop-silent */ }
|
|
1017
1017
|
if (!res.headersSent) {
|
|
1018
|
-
res.writeHead(
|
|
1018
|
+
res.writeHead(C.HTTP.STATUS.CONTENT_TOO_LARGE, { "Content-Type": "text/plain" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
1019
1019
|
res.end("RFC 8460 §5.4 — body exceeds " + maxCompressed + " bytes\n");
|
|
1020
1020
|
}
|
|
1021
1021
|
void e; // _e shadowed by lower scope; mark intent
|
|
@@ -1051,7 +1051,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
1051
1051
|
if (onRefuse) try { onRefuse("mail-tlsrpt/untrusted-reporter",
|
|
1052
1052
|
"reporter '" + report["organization-name"] + "' not in trustedReporters", req); }
|
|
1053
1053
|
catch (_e) { /* drop-silent */ }
|
|
1054
|
-
res.writeHead(
|
|
1054
|
+
res.writeHead(C.HTTP.STATUS.FORBIDDEN, { "Content-Type": "text/plain", "Error-Type": "mail-tlsrpt/untrusted-reporter" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
1055
1055
|
res.end("RFC 8460 §5.3-class: untrusted reporter\n");
|
|
1056
1056
|
return;
|
|
1057
1057
|
}
|
|
@@ -1072,12 +1072,12 @@ function tlsRptIngestHttp(opts) {
|
|
|
1072
1072
|
if (ret && typeof ret.then === "function") {
|
|
1073
1073
|
ret.then(function () {
|
|
1074
1074
|
if (!res.headersSent) {
|
|
1075
|
-
res.writeHead(
|
|
1075
|
+
res.writeHead(C.HTTP.STATUS.CREATED, { "Content-Type": "text/plain" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
1076
1076
|
res.end("RFC 8460 §5.4 — accepted\n");
|
|
1077
1077
|
}
|
|
1078
1078
|
}, function (_e) {
|
|
1079
1079
|
if (!res.headersSent) {
|
|
1080
|
-
res.writeHead(
|
|
1080
|
+
res.writeHead(C.HTTP.STATUS.INTERNAL_SERVER_ERROR, { "Content-Type": "text/plain" }); // internal-error status
|
|
1081
1081
|
res.end("internal error processing report\n");
|
|
1082
1082
|
}
|
|
1083
1083
|
});
|
|
@@ -1085,7 +1085,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
1085
1085
|
}
|
|
1086
1086
|
} catch (_e) { /* fall through to 201 — operator hook is best-effort */ }
|
|
1087
1087
|
}
|
|
1088
|
-
res.writeHead(
|
|
1088
|
+
res.writeHead(C.HTTP.STATUS.CREATED, { "Content-Type": "text/plain" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
1089
1089
|
res.end("RFC 8460 §5.4 — accepted\n");
|
|
1090
1090
|
});
|
|
1091
1091
|
req.on("error", function () {
|
|
@@ -1093,7 +1093,7 @@ function tlsRptIngestHttp(opts) {
|
|
|
1093
1093
|
aborted = true;
|
|
1094
1094
|
_safeAuditEmit(opts.audit, "mail.tlsrpt.ingest_http", "denied", { reason: "req-error" });
|
|
1095
1095
|
if (!res.headersSent) {
|
|
1096
|
-
res.writeHead(
|
|
1096
|
+
res.writeHead(C.HTTP.STATUS.BAD_REQUEST, { "Content-Type": "text/plain" }); // allow:raw-time-literal — RFC 8460 §5.4 status code
|
|
1097
1097
|
res.end("malformed request\n");
|
|
1098
1098
|
}
|
|
1099
1099
|
});
|