@blamejs/blamejs-shop 0.5.12 → 0.5.13

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.
Files changed (521) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/vendor/MANIFEST.json +632 -416
  4. package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
  5. package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
  6. package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
  7. package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
  8. package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
  9. package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
  10. package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
  11. package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
  12. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
  13. package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
  14. package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
  15. package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
  16. package/lib/vendor/blamejs/.gitignore +2 -0
  17. package/lib/vendor/blamejs/CHANGELOG.md +122 -0
  18. package/lib/vendor/blamejs/NOTICE +26 -1
  19. package/lib/vendor/blamejs/SECURITY.md +1 -1
  20. package/lib/vendor/blamejs/api-snapshot.json +207 -5
  21. package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
  22. package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
  23. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
  24. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
  25. package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
  26. package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
  27. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
  28. package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
  29. package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
  30. package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
  31. package/lib/vendor/blamejs/index.js +6 -0
  32. package/lib/vendor/blamejs/lib/a2a.js +4 -4
  33. package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
  34. package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
  35. package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
  36. package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
  37. package/lib/vendor/blamejs/lib/archive-read.js +8 -0
  38. package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
  39. package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
  40. package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
  41. package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
  42. package/lib/vendor/blamejs/lib/audit.js +71 -8
  43. package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
  44. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
  45. package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
  46. package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
  47. package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
  48. package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
  49. package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
  50. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
  51. package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
  52. package/lib/vendor/blamejs/lib/backup/index.js +16 -5
  53. package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
  54. package/lib/vendor/blamejs/lib/base32.js +21 -0
  55. package/lib/vendor/blamejs/lib/break-glass.js +19 -10
  56. package/lib/vendor/blamejs/lib/bundler.js +5 -1
  57. package/lib/vendor/blamejs/lib/cache-status.js +3 -7
  58. package/lib/vendor/blamejs/lib/calendar.js +21 -1
  59. package/lib/vendor/blamejs/lib/cbor.js +12 -1
  60. package/lib/vendor/blamejs/lib/cert.js +29 -11
  61. package/lib/vendor/blamejs/lib/cli.js +94 -24
  62. package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
  63. package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
  64. package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
  65. package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
  66. package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
  67. package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
  68. package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
  69. package/lib/vendor/blamejs/lib/content-digest.js +2 -1
  70. package/lib/vendor/blamejs/lib/cookies.js +13 -3
  71. package/lib/vendor/blamejs/lib/cose.js +31 -16
  72. package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
  73. package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
  74. package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
  75. package/lib/vendor/blamejs/lib/crypto.js +314 -19
  76. package/lib/vendor/blamejs/lib/csp.js +11 -3
  77. package/lib/vendor/blamejs/lib/daemon.js +270 -24
  78. package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
  79. package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
  80. package/lib/vendor/blamejs/lib/db-query.js +34 -7
  81. package/lib/vendor/blamejs/lib/db.js +18 -6
  82. package/lib/vendor/blamejs/lib/dbsc.js +17 -0
  83. package/lib/vendor/blamejs/lib/dev.js +21 -2
  84. package/lib/vendor/blamejs/lib/dsr.js +157 -102
  85. package/lib/vendor/blamejs/lib/external-db.js +41 -2
  86. package/lib/vendor/blamejs/lib/file-upload.js +25 -7
  87. package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
  88. package/lib/vendor/blamejs/lib/forms.js +113 -27
  89. package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
  90. package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
  91. package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
  92. package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
  93. package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
  94. package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
  95. package/lib/vendor/blamejs/lib/guard-html.js +26 -53
  96. package/lib/vendor/blamejs/lib/guard-image.js +24 -5
  97. package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
  98. package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
  99. package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
  100. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
  101. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
  102. package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
  103. package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
  104. package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
  105. package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
  106. package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
  107. package/lib/vendor/blamejs/lib/guard-time.js +1 -1
  108. package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
  109. package/lib/vendor/blamejs/lib/html-balance.js +3 -3
  110. package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
  111. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
  112. package/lib/vendor/blamejs/lib/http-client.js +39 -7
  113. package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
  114. package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
  115. package/lib/vendor/blamejs/lib/i18n.js +35 -21
  116. package/lib/vendor/blamejs/lib/incident-report.js +8 -1
  117. package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
  118. package/lib/vendor/blamejs/lib/json-path.js +23 -1
  119. package/lib/vendor/blamejs/lib/jwk.js +20 -3
  120. package/lib/vendor/blamejs/lib/link-header.js +2 -1
  121. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
  122. package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
  123. package/lib/vendor/blamejs/lib/log-stream.js +8 -3
  124. package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
  125. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  126. package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
  127. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
  128. package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
  129. package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
  130. package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
  131. package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
  132. package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
  133. package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
  134. package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
  135. package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
  136. package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
  137. package/lib/vendor/blamejs/lib/mail.js +15 -5
  138. package/lib/vendor/blamejs/lib/metrics.js +268 -63
  139. package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
  140. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
  141. package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
  142. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
  143. package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
  144. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
  145. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
  146. package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
  147. package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
  148. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
  149. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
  150. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
  151. package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
  152. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
  153. package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
  154. package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
  155. package/lib/vendor/blamejs/lib/network-dns.js +21 -2
  156. package/lib/vendor/blamejs/lib/network-tls.js +69 -7
  157. package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
  158. package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
  159. package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
  160. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
  161. package/lib/vendor/blamejs/lib/outbox.js +1 -1
  162. package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
  163. package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
  164. package/lib/vendor/blamejs/lib/permissions.js +6 -3
  165. package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
  166. package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
  167. package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
  168. package/lib/vendor/blamejs/lib/pubsub.js +20 -9
  169. package/lib/vendor/blamejs/lib/queue-local.js +8 -1
  170. package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
  171. package/lib/vendor/blamejs/lib/redact.js +54 -0
  172. package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
  173. package/lib/vendor/blamejs/lib/restore.js +19 -0
  174. package/lib/vendor/blamejs/lib/retention.js +12 -4
  175. package/lib/vendor/blamejs/lib/router.js +60 -6
  176. package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
  177. package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
  178. package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
  179. package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
  180. package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
  181. package/lib/vendor/blamejs/lib/safe-object.js +80 -0
  182. package/lib/vendor/blamejs/lib/safe-path.js +52 -4
  183. package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
  184. package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
  185. package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
  186. package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
  187. package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
  188. package/lib/vendor/blamejs/lib/scheduler.js +8 -1
  189. package/lib/vendor/blamejs/lib/security-assert.js +6 -6
  190. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
  191. package/lib/vendor/blamejs/lib/self-update.js +517 -87
  192. package/lib/vendor/blamejs/lib/server-timing.js +2 -4
  193. package/lib/vendor/blamejs/lib/session.js +34 -12
  194. package/lib/vendor/blamejs/lib/sql.js +15 -1
  195. package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
  196. package/lib/vendor/blamejs/lib/static.js +55 -13
  197. package/lib/vendor/blamejs/lib/storage.js +8 -2
  198. package/lib/vendor/blamejs/lib/subject.js +6 -1
  199. package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
  200. package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
  201. package/lib/vendor/blamejs/lib/tsa.js +34 -13
  202. package/lib/vendor/blamejs/lib/uri-template.js +8 -1
  203. package/lib/vendor/blamejs/lib/vault/index.js +13 -0
  204. package/lib/vendor/blamejs/lib/vc.js +44 -5
  205. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
  206. package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
  207. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
  208. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
  209. package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
  210. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
  211. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
  212. package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
  213. package/lib/vendor/blamejs/lib/watcher.js +89 -17
  214. package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
  215. package/lib/vendor/blamejs/lib/webhook.js +12 -2
  216. package/lib/vendor/blamejs/lib/ws-client.js +32 -1
  217. package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
  218. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
  219. package/lib/vendor/blamejs/package-lock.json +2 -2
  220. package/lib/vendor/blamejs/package.json +1 -1
  221. package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
  222. package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
  223. package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
  224. package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
  225. package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
  226. package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
  227. package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
  228. package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
  229. package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
  230. package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
  231. package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
  232. package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
  233. package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
  234. package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
  235. package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
  236. package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
  237. package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
  238. package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
  239. package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
  240. package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
  241. package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
  242. package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
  243. package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
  244. package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
  245. package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
  246. package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
  247. package/lib/vendor/blamejs/socket.yml +94 -0
  248. package/lib/vendor/blamejs/test/00-primitives.js +605 -0
  249. package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
  250. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
  251. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
  252. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
  253. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
  254. package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
  255. package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
  256. package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
  257. package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
  258. package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
  259. package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
  262. package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
  268. package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
  270. package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
  283. package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
  284. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
  293. package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
  294. package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
  302. package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
  307. package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
  315. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
  317. package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
  318. package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
  319. package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
  320. package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
  321. package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
  322. package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
  323. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
  324. package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
  325. package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
  326. package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
  327. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
  328. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
  329. package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
  330. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
  331. package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
  332. package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
  333. package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
  334. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
  335. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
  336. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
  337. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
  338. package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
  339. package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
  340. package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
  341. package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
  342. package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
  343. package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
  344. package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
  345. package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
  346. package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
  347. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
  348. package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
  349. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
  350. package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
  351. package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
  352. package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
  353. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
  354. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
  355. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
  356. package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
  357. package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
  358. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
  359. package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
  360. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
  361. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
  362. package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
  363. package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
  364. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
  365. package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
  366. package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
  367. package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
  368. package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
  369. package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
  370. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
  371. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
  372. package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
  373. package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
  374. package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
  375. package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
  376. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
  377. package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
  378. package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
  379. package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
  380. package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
  381. package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
  382. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
  383. package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
  384. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
  385. package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
  386. package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
  387. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
  388. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
  389. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
  390. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
  391. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
  392. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
  393. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
  394. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
  395. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
  396. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
  397. package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
  398. package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
  399. package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
  400. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
  401. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
  402. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
  403. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
  404. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
  405. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
  406. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
  407. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
  408. package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
  409. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
  410. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
  411. package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
  412. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
  413. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
  414. package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
  415. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
  416. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
  417. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
  418. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
  419. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
  420. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
  421. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
  422. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
  423. package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
  424. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
  425. package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
  426. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
  427. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
  428. package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
  429. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
  430. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
  431. package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
  432. package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
  433. package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
  434. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
  435. package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
  436. package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
  437. package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
  438. package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
  439. package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
  440. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
  441. package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
  442. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
  443. package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
  444. package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
  445. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
  446. package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
  447. package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
  448. package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
  449. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
  450. package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
  451. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
  452. package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
  453. package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
  454. package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
  455. package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
  456. package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
  457. package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
  458. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
  459. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
  460. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
  461. package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
  462. package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
  463. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
  464. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
  465. package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
  466. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
  467. package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
  468. package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
  469. package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
  470. package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
  471. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
  472. package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
  473. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
  474. package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
  475. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
  476. package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
  477. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
  478. package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
  479. package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
  480. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
  481. package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
  482. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
  483. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
  484. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
  485. package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
  486. package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
  487. package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
  488. package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
  489. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
  490. package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
  491. package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
  492. package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
  493. package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
  494. package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
  495. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
  496. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
  497. package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
  498. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
  499. package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
  500. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
  501. package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
  502. package/lib/vendor/blamejs/test/smoke.js +2 -0
  503. package/package.json +1 -1
  504. package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
  505. package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
  506. package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
  507. package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
  508. package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
  509. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
  510. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
  511. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
  512. package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
  513. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
  514. package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
  515. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
  516. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
  517. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
  518. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
  519. package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
  520. package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
  521. package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
@@ -6,15 +6,24 @@
6
6
  * RFC 1870 SIZE pre-check, IPv6 connect family preference, and the
7
7
  * b.mail.reverseDns FCrDNS helper. Mock SMTP server scripts EHLO
8
8
  * advertisement to exercise the transport branches without a live
9
- * relay. The standalone CLI runs ad-hoc; smoke wires this in as a
10
- * layer-0 file.
9
+ * relay. Also drives the error / defensive / adversarial branches of
10
+ * the wider b.mail surface — toAscii/toUnicode, the RFC 822 builder,
11
+ * console/memory/http/resend transports, create() validation, and the
12
+ * full SMTP state machine over a loopback TLS server whose leaf cert
13
+ * is minted by b.mtlsEngine and trusted via opts.ca (verification
14
+ * stays ON — no rejectUnauthorized bypass). The standalone CLI runs
15
+ * ad-hoc; smoke wires this in as a layer-0 file.
11
16
  */
12
17
 
18
+ var http = require("node:http");
13
19
  var net = require("net");
20
+ var tls = require("node:tls");
14
21
  var helpers = require("../helpers");
15
22
  var b = helpers.b;
16
23
  var check = helpers.check;
17
24
 
25
+ var C = b.constants;
26
+
18
27
  function listenOnRandomPort(server) {
19
28
  return new Promise(function (resolve, reject) {
20
29
  server.listen(0, "127.0.0.1", function () { resolve(server.address().port); });
@@ -415,6 +424,1518 @@ async function testGuardDomainValidatesFromAddress() {
415
424
  /from domain/.test(threw.message));
416
425
  }
417
426
 
427
+ // ---------------------------------------------------------------------------
428
+ // Loopback SMTP mock — a scriptable RFC 5321 state machine over an
429
+ // already-secure socket (implicit TLS) or a cleartext socket that
430
+ // upgrades on STARTTLS. Captures every wire line + BDAT chunks.
431
+ // ---------------------------------------------------------------------------
432
+
433
+ function _wireHandler(sock, script, state) {
434
+ sock.setEncoding("utf8");
435
+ var pending = Buffer.alloc(0);
436
+ var inData = false;
437
+ var dataBuf = "";
438
+ var bdatRemaining = 0;
439
+ var bdatLast = false;
440
+ var bdatBody = Buffer.alloc(0);
441
+ function reply(code, text) { sock.write(code + " " + text + "\r\n"); }
442
+ sock.on("data", function (chunk) {
443
+ pending = Buffer.concat([pending, Buffer.from(chunk, "utf8")]);
444
+ while (true) {
445
+ if (bdatRemaining > 0) {
446
+ if (pending.length === 0) return;
447
+ var take = Math.min(bdatRemaining, pending.length);
448
+ bdatBody = Buffer.concat([bdatBody, pending.slice(0, take)]);
449
+ pending = pending.slice(take);
450
+ bdatRemaining -= take;
451
+ if (bdatRemaining === 0) {
452
+ state.bdatChunks.push({ size: bdatBody.length, last: bdatLast });
453
+ bdatBody = Buffer.alloc(0);
454
+ reply(script.bdatCode || 250, "chunk ok");
455
+ }
456
+ continue;
457
+ }
458
+ if (inData) {
459
+ dataBuf += pending.toString("utf8");
460
+ pending = Buffer.alloc(0);
461
+ if (dataBuf.indexOf("\r\n.\r\n") >= 0) {
462
+ inData = false; dataBuf = "";
463
+ reply(script.bodyCode || 250, "message accepted");
464
+ }
465
+ return;
466
+ }
467
+ var nl = pending.indexOf("\r\n");
468
+ if (nl < 0) return;
469
+ var line = pending.slice(0, nl).toString("utf8");
470
+ pending = pending.slice(nl + 2);
471
+ if (!line) continue;
472
+ state.lines.push(line);
473
+ var u = line.toUpperCase();
474
+ if (u.indexOf("EHLO") === 0) {
475
+ if (script.noEhloResponse) return;
476
+ var ext = script.ext || [];
477
+ var resp = "250-mock greets you\r\n";
478
+ for (var i = 0; i < ext.length; i += 1) {
479
+ resp += ((i === ext.length - 1) ? "250 " : "250-") + ext[i] + "\r\n";
480
+ }
481
+ if (ext.length === 0) resp = (script.ehloCode || 250) + " mock\r\n";
482
+ else if (script.ehloCode && script.ehloCode !== 250) {
483
+ resp = script.ehloCode + " ehlo rejected\r\n";
484
+ }
485
+ sock.write(resp);
486
+ } else if (u.indexOf("AUTH LOGIN") === 0) {
487
+ reply(script.authUserCode || 334, "VXNlcm5hbWU6");
488
+ } else if (u.indexOf("MAIL FROM") === 0) {
489
+ state.mailFromLine = line;
490
+ reply(script.mailFromCode || 250, "sender ok");
491
+ } else if (u.indexOf("RCPT TO") === 0) {
492
+ state.rcptLines.push(line);
493
+ reply(script.rcptCode || 250, "rcpt ok");
494
+ } else if (u === "DATA") {
495
+ if (script.dataCode && script.dataCode !== 354) {
496
+ reply(script.dataCode, "data rejected");
497
+ } else { inData = true; reply(354, "send body"); }
498
+ } else if (u.indexOf("BDAT ") === 0) {
499
+ var parts = line.split(/\s+/);
500
+ var n = parseInt(parts[1], 10);
501
+ bdatRemaining = isFinite(n) && n >= 0 ? n : 0;
502
+ bdatLast = (parts.length >= 3 && parts[2].toUpperCase() === "LAST");
503
+ if (bdatRemaining === 0) {
504
+ state.bdatChunks.push({ size: 0, last: bdatLast });
505
+ reply(script.bdatCode || 250, "empty chunk ok");
506
+ }
507
+ } else if (u === "QUIT") {
508
+ reply(221, "bye"); sock.end();
509
+ } else if (state.authStage !== undefined) {
510
+ // AUTH LOGIN username/password base64 lines.
511
+ state.authLines.push(line);
512
+ if (state.authStage === 0) {
513
+ state.authStage = 1;
514
+ reply(script.authPassCode || 334, "UGFzc3dvcmQ6");
515
+ } else {
516
+ reply(script.authFinalCode || 235, "auth ok");
517
+ }
518
+ } else {
519
+ reply(250, "ok");
520
+ }
521
+ // Track AUTH LOGIN follow-up lines: after "AUTH LOGIN" the next two
522
+ // non-command lines are user + pass base64.
523
+ if (u.indexOf("AUTH LOGIN") === 0) state.authStage = 0;
524
+ }
525
+ });
526
+ sock.on("error", function () { /* client teardown is expected */ });
527
+ }
528
+
529
+ function _newState() {
530
+ return {
531
+ lines: [], mailFromLine: null, rcptLines: [], bdatChunks: [],
532
+ authLines: [], authStage: undefined,
533
+ };
534
+ }
535
+
536
+ // Implicit-TLS SMTP server.
537
+ function startTlsSmtp(certPair, script) {
538
+ var state = _newState();
539
+ var server = tls.createServer({ key: certPair.key, cert: certPair.cert }, function (sock) {
540
+ if (typeof script.greetingFlood === "number") {
541
+ sock.write(Buffer.alloc(script.greetingFlood, 0x61).toString("utf8")); // allow:raw-byte-literal — 'a' filler for oversize-framing test
542
+ sock.on("error", function () {});
543
+ return;
544
+ }
545
+ sock.write((script.greeting || "220 mock ESMTP") + "\r\n");
546
+ if (script.greetingReject) { sock.on("error", function () {}); return; }
547
+ _wireHandler(sock, script, state);
548
+ });
549
+ state.server = server;
550
+ return state;
551
+ }
552
+
553
+ // Cleartext SMTP server that upgrades to TLS on STARTTLS.
554
+ function startStarttlsSmtp(certPair, script) {
555
+ var state = _newState();
556
+ var server = net.createServer(function (raw) {
557
+ raw.setEncoding("utf8");
558
+ raw.write("220 mock ESMTP\r\n");
559
+ var buf = "";
560
+ raw.on("data", function onData(d) {
561
+ buf += d;
562
+ var idx;
563
+ while ((idx = buf.indexOf("\r\n")) >= 0) {
564
+ var line = buf.slice(0, idx);
565
+ buf = buf.slice(idx + 2);
566
+ if (!line) continue;
567
+ state.lines.push(line);
568
+ var u = line.toUpperCase();
569
+ if (u.indexOf("EHLO") === 0) {
570
+ raw.write("250-mock\r\n250 STARTTLS\r\n");
571
+ } else if (u.indexOf("STARTTLS") === 0) {
572
+ if (script.starttls === "reject") { raw.write("502 no starttls\r\n"); continue; }
573
+ raw.write("220 go ahead\r\n");
574
+ raw.removeListener("data", onData);
575
+ var upgraded = new tls.TLSSocket(raw, {
576
+ isServer: true, key: certPair.key, cert: certPair.cert,
577
+ });
578
+ _wireHandler(upgraded, script, state);
579
+ return;
580
+ }
581
+ }
582
+ });
583
+ raw.on("error", function () {});
584
+ });
585
+ state.server = server;
586
+ return state;
587
+ }
588
+
589
+ function listen(state) {
590
+ return new Promise(function (resolve, reject) {
591
+ state.server.listen(0, "127.0.0.1", function () {
592
+ state.port = state.server.address().port;
593
+ resolve(state.port);
594
+ });
595
+ state.server.on("error", reject);
596
+ });
597
+ }
598
+
599
+ function closeServer(state) {
600
+ return new Promise(function (resolve) {
601
+ try { state.server.close(function () { resolve(); }); }
602
+ catch (_e) { resolve(); }
603
+ });
604
+ }
605
+
606
+ // A TLS smtp transport pointed at a loopback mock; verification stays on
607
+ // via opts.ca + opts.servername:"localhost" (cert carries a localhost SAN).
608
+ function tlsTransport(certPair, port, extra) {
609
+ var opts = {
610
+ host: "127.0.0.1", port: port, implicitTls: true,
611
+ ca: certPair.caCertPem, servername: "localhost", preferFamily: 4,
612
+ timeoutMs: C.TIME.seconds(4),
613
+ };
614
+ if (extra) { for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) opts[k] = extra[k]; }
615
+ return b.mail.transports.smtp(opts);
616
+ }
617
+
618
+ function starttlsTransport(certPair, port, extra) {
619
+ var opts = {
620
+ host: "127.0.0.1", port: port,
621
+ ca: certPair.caCertPem, servername: "localhost", preferFamily: 4,
622
+ timeoutMs: C.TIME.seconds(4),
623
+ };
624
+ if (extra) { for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) opts[k] = extra[k]; }
625
+ return b.mail.transports.smtp(opts);
626
+ }
627
+
628
+ async function _sendErr(transport, message) {
629
+ var err = null;
630
+ try { await transport.send(message); }
631
+ catch (e) { err = e; }
632
+ return err;
633
+ }
634
+
635
+ // ---------------------------------------------------------------------------
636
+ // Pure helpers: toAscii / toUnicode
637
+ // ---------------------------------------------------------------------------
638
+
639
+ function testToAsciiBranches() {
640
+ check("toAscii(non-string) → null", b.mail.toAscii(123) === null);
641
+ check("toAscii('') → null", b.mail.toAscii("") === null);
642
+ check("toAscii with '/' → null", b.mail.toAscii("a.com/evil") === null);
643
+ check("toAscii with '?' → null", b.mail.toAscii("a.com?x") === null);
644
+ check("toAscii with '#' → null", b.mail.toAscii("a.com#x") === null);
645
+ check("toAscii with '\\\\' → null", b.mail.toAscii("a.com\\x") === null);
646
+ check("toAscii with ':' → null", b.mail.toAscii("a.com:25") === null);
647
+ check("toAscii with '@' → null", b.mail.toAscii("u@a.com") === null);
648
+ check("toAscii with '[' → null", b.mail.toAscii("[1.2.3.4]") === null);
649
+ check("toAscii IDN → xn--", b.mail.toAscii("münchen.de") === "xn--mnchen-3ya.de");
650
+ }
651
+
652
+ function testToUnicodeBranches() {
653
+ check("toUnicode(non-string) → null", b.mail.toUnicode(123) === null);
654
+ check("toUnicode('') → null", b.mail.toUnicode("") === null);
655
+ check("toUnicode xn-- → unicode", b.mail.toUnicode("xn--mnchen-3ya.de") === "münchen.de");
656
+ }
657
+
658
+ // ---------------------------------------------------------------------------
659
+ // _buildRfc822ForTest — multipart, calendar, attachments, dot-stuffing
660
+ // ---------------------------------------------------------------------------
661
+
662
+ function testBuildRfc822MultipartAlternative() {
663
+ var wire = b.mail._buildRfc822ForTest({
664
+ from: "a@x.com", to: "b@y.com", subject: "s",
665
+ text: "plain text", html: "<p>html</p>",
666
+ });
667
+ check("alt: multipart/alternative content-type",
668
+ /Content-Type: multipart\/alternative; boundary="/.test(wire));
669
+ check("alt: carries text part", wire.indexOf("text/plain; charset=utf-8") !== -1);
670
+ check("alt: carries html part", wire.indexOf("text/html; charset=utf-8") !== -1);
671
+ check("alt: closes boundary", /--blamejs-alt-[^\r\n]+--/.test(wire));
672
+ }
673
+
674
+ function testBuildRfc822CalendarCcReplyToHeaders() {
675
+ var wire = b.mail._buildRfc822ForTest({
676
+ from: "a@x.com", to: ["b@y.com", "c@y.com"], cc: ["d@y.com"],
677
+ replyTo: "reply@x.com", subject: "invite",
678
+ headers: { "X-Custom": "vv" },
679
+ calendar: { method: "REQUEST", icalText: "BEGIN:VCALENDAR\r\nEND:VCALENDAR" },
680
+ });
681
+ check("cal: To joins array", /^To: b@y\.com, c@y\.com/m.test(wire));
682
+ check("cal: Cc header present", /^Cc: d@y\.com/m.test(wire));
683
+ check("cal: Reply-To header present", /^Reply-To: reply@x\.com/m.test(wire));
684
+ check("cal: custom header present", /^X-Custom: vv/m.test(wire));
685
+ check("cal: text/calendar with method param",
686
+ wire.indexOf('text/calendar; method="REQUEST"') !== -1);
687
+ }
688
+
689
+ function testBuildRfc822AttachmentsAndDotStuffing() {
690
+ var wire = b.mail._buildRfc822ForTest({
691
+ from: "a@x.com", to: "b@y.com", subject: "s",
692
+ text: ".leading dot line\nsecond",
693
+ attachments: [
694
+ { filename: "a.txt", content: "hello", contentType: "text/plain" },
695
+ { filename: "logo.png", content: Buffer.from([0x89, 0x50]), cid: "img1" }, // allow:raw-byte-literal — 2-byte fixture
696
+ ],
697
+ });
698
+ check("att: multipart/mixed wrapper", /Content-Type: multipart\/mixed; boundary="/.test(wire));
699
+ check("att: base64 transfer encoding", wire.indexOf("Content-Transfer-Encoding: base64") !== -1);
700
+ check("att: filename param", wire.indexOf('filename="a.txt"') !== -1);
701
+ check("att: inline cid → Content-ID", wire.indexOf("Content-ID: <img1>") !== -1);
702
+ check("att: inline disposition for cid part", wire.indexOf("Content-Disposition: inline") !== -1);
703
+ check("att: built message body is NOT pre-dot-stuffed (DATA transparency moved to DATA send; BDAT gets raw)",
704
+ wire.indexOf("\r\n.leading dot line") !== -1 && wire.indexOf("\r\n..leading dot line") === -1);
705
+ }
706
+
707
+ // ---------------------------------------------------------------------------
708
+ // console + memory transports
709
+ // ---------------------------------------------------------------------------
710
+
711
+ function _fakeStream() {
712
+ var out = { data: "" };
713
+ out.write = function (s) { out.data += s; };
714
+ return out;
715
+ }
716
+
717
+ async function testConsoleTransport() {
718
+ var s1 = _fakeStream();
719
+ var t1 = b.mail.transports.console({ stream: s1 });
720
+ var r1 = await t1.send({
721
+ to: ["a@x.com", "b@x.com"], from: "f@x.com", subject: "hi",
722
+ cc: ["c@x.com"], bcc: ["secret@x.com"], text: "body-text",
723
+ });
724
+ check("console: returns transport marker", r1.transport === "console" && typeof r1.deliveredAt === "number");
725
+ check("console: prints To joined", s1.data.indexOf("To: a@x.com, b@x.com") !== -1);
726
+ check("console: prints Cc", s1.data.indexOf("Cc: c@x.com") !== -1);
727
+ check("console: prints Bcc addresses (redact off)", s1.data.indexOf("Bcc: secret@x.com") !== -1);
728
+ check("console: prints body", s1.data.indexOf("body-text") !== -1);
729
+
730
+ var s2 = _fakeStream();
731
+ var t2 = b.mail.transports.console({ stream: s2, redactBcc: true });
732
+ await t2.send({ to: "a@x.com", from: "f@x.com", bcc: ["x@x.com", "y@x.com"], html: "<b>hi</b>" });
733
+ check("console: redactBcc hides addresses", s2.data.indexOf("x@x.com") === -1);
734
+ check("console: redactBcc shows count", s2.data.indexOf("2 recipients") !== -1);
735
+ check("console: html-only body renders size hint", s2.data.indexOf("(html body,") !== -1);
736
+
737
+ // Single (non-array) recipient + single bcc branch.
738
+ var s3 = _fakeStream();
739
+ var t3 = b.mail.transports.console({ stream: s3, redactBcc: true });
740
+ await t3.send({ to: "solo@x.com", from: "f@x.com", bcc: "one@x.com" });
741
+ check("console: single bcc redacts to '1 recipient'", s3.data.indexOf("1 recipient —") !== -1);
742
+ }
743
+
744
+ async function testMemoryTransport() {
745
+ var mem = b.mail.transports.memory();
746
+ var r0 = await mem.send({ to: "a@x.com", from: "f@x.com" });
747
+ var r1 = await mem.send({ to: "b@x.com", from: "f@x.com" });
748
+ check("memory: captures sends", mem.sent.length === 2);
749
+ check("memory: index reflects order", r0.index === 0 && r1.index === 1);
750
+ check("memory: transport marker", r0.transport === "memory");
751
+ mem.reset();
752
+ check("memory: reset clears sent", mem.sent.length === 0);
753
+ }
754
+
755
+ // ---------------------------------------------------------------------------
756
+ // httpTransport — config gates, serializer faults, error wrap, interpret
757
+ // ---------------------------------------------------------------------------
758
+
759
+ function testHttpTransportConfigGates() {
760
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
761
+ check("http: requires endpoint",
762
+ threw(function () { b.mail.transports.http({ serialize: function () {} }); }).code === "mail/http-misconfigured");
763
+ check("http: requires serialize fn",
764
+ threw(function () { b.mail.transports.http({ endpoint: "https://x.test" }); }).code === "mail/http-misconfigured");
765
+ }
766
+
767
+ async function testHttpTransportBadSerializer() {
768
+ var t1 = b.mail.transports.http({
769
+ endpoint: "https://x.test", name: "vend",
770
+ serialize: function () { return null; },
771
+ });
772
+ var e1 = await _sendErr(t1, { to: "a@x.com", from: "f@x.com" });
773
+ check("http: serialize non-object → bad-serializer", e1 && e1.code === "mail/vend-bad-serializer");
774
+
775
+ var t2 = b.mail.transports.http({
776
+ endpoint: "https://x.test", name: "vend",
777
+ serialize: function () { return { body: 42 }; },
778
+ });
779
+ var e2 = await _sendErr(t2, { to: "a@x.com", from: "f@x.com" });
780
+ check("http: serialize body non-string/Buffer → bad-serializer", e2 && e2.code === "mail/vend-bad-serializer");
781
+ }
782
+
783
+ async function testHttpTransportRequestFailureWrap() {
784
+ // Point at a closed loopback port → ECONNREFUSED inside http-client →
785
+ // rewrapped into mail/<name>-failed with the original as cause.
786
+ var t = b.mail.transports.http({
787
+ endpoint: "http://127.0.0.1:1/send", name: "vend",
788
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
789
+ serialize: function () { return { body: "{}" }; },
790
+ });
791
+ var e = await _sendErr(t, { to: "a@x.com", from: "f@x.com" });
792
+ check("http: request failure → mail/vend-failed", e && e.code === "mail/vend-failed");
793
+ check("http: original error preserved as cause", e && e.cause != null);
794
+ }
795
+
796
+ async function testHttpTransportSuccessAndInterpret() {
797
+ // Loopback HTTP server returns a controllable status + body; interpret
798
+ // branches drive off it.
799
+ var server = http.createServer(function (req, res) {
800
+ var chunks = [];
801
+ req.on("data", function (c) { chunks.push(c); });
802
+ req.on("end", function () {
803
+ var mode = req.headers["x-mode"];
804
+ if (mode === "500") { res.writeHead(500); res.end("err"); return; }
805
+ res.writeHead(200, { "Content-Type": "application/json" });
806
+ res.end(JSON.stringify({ id: "msg-123", extraKey: "ev" }));
807
+ });
808
+ });
809
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
810
+ var port = server.address().port;
811
+ var base = {
812
+ endpoint: "http://127.0.0.1:" + port + "/send", name: "vend",
813
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
814
+ };
815
+ try {
816
+ // (a) No interpret → returns info + statusCode.
817
+ var tNo = b.mail.transports.http(Object.assign({}, base, {
818
+ serialize: function () { return { body: "{}" }; },
819
+ }));
820
+ var rNo = await tNo.send({ to: "a@x.com", from: "f@x.com" });
821
+ check("http: no-interpret returns info", rNo.transport === "vend" && rNo.statusCode === 200);
822
+
823
+ // (b) interpret ok with id + extra merged.
824
+ var tOk = b.mail.transports.http(Object.assign({}, base, {
825
+ serialize: function () { return { headers: { "Content-Length": "2" }, body: "{}" }; },
826
+ interpret: function (res) {
827
+ var d = JSON.parse(res.body.toString("utf8"));
828
+ return { ok: true, id: d.id, extra: { extraKey: d.extraKey } };
829
+ },
830
+ }));
831
+ var rOk = await tOk.send({ to: "a@x.com", from: "f@x.com" });
832
+ check("http: interpret id merged", rOk.id === "msg-123");
833
+ check("http: interpret extra merged", rOk.extraKey === "ev");
834
+
835
+ // (c) interpret verdict false → rejected with statusCode.
836
+ var tRej = b.mail.transports.http(Object.assign({}, base, {
837
+ serialize: function () { return { body: "{}" }; },
838
+ interpret: function () { return { ok: false, reason: "nope", statusCode: 422 }; },
839
+ }));
840
+ var eRej = await _sendErr(tRej, { to: "a@x.com", from: "f@x.com" });
841
+ check("http: verdict false → rejected", eRej && eRej.code === "mail/vend-rejected");
842
+ check("http: rejected carries statusCode", eRej && eRej.statusCode === 422);
843
+
844
+ // (d) interpret throws plain → interpret-failed.
845
+ var tThrow = b.mail.transports.http(Object.assign({}, base, {
846
+ serialize: function () { return { body: "{}" }; },
847
+ interpret: function () { throw new Error("boom"); },
848
+ }));
849
+ var eThrow = await _sendErr(tThrow, { to: "a@x.com", from: "f@x.com" });
850
+ check("http: interpret throw → interpret-failed", eThrow && eThrow.code === "mail/vend-interpret-failed");
851
+
852
+ // (e) interpret throws a MailError → passthrough.
853
+ var tMail = b.mail.transports.http(Object.assign({}, base, {
854
+ serialize: function () { return { body: "{}" }; },
855
+ interpret: function () { throw new b.mail.MailError("mail/custom-code", "x", true); },
856
+ }));
857
+ var eMail = await _sendErr(tMail, { to: "a@x.com", from: "f@x.com" });
858
+ check("http: interpret MailError passthrough", eMail && eMail.code === "mail/custom-code");
859
+
860
+ // (f) non-2xx status → http-client throws → mail/vend-failed with statusCode.
861
+ var tErr = b.mail.transports.http(Object.assign({}, base, {
862
+ serialize: function () { return { headers: { "X-Mode": "500" }, body: "{}" }; },
863
+ }));
864
+ var eErr = await _sendErr(tErr, { to: "a@x.com", from: "f@x.com" });
865
+ check("http: non-2xx → mail/vend-failed", eErr && eErr.code === "mail/vend-failed");
866
+ } finally {
867
+ await new Promise(function (r) { server.close(function () { r(); }); });
868
+ }
869
+ }
870
+
871
+ // ---------------------------------------------------------------------------
872
+ // resendTransport — config gate + serialize + interpret over loopback
873
+ // ---------------------------------------------------------------------------
874
+
875
+ function testResendConfigGate() {
876
+ var threw = null;
877
+ try { b.mail.transports.resend({}); } catch (e) { threw = e; }
878
+ check("resend: requires apiKey", threw && threw.code === "mail/resend-misconfigured");
879
+ }
880
+
881
+ async function testResendOverLoopback() {
882
+ var lastBody = { v: null };
883
+ var server = http.createServer(function (req, res) {
884
+ var chunks = [];
885
+ req.on("data", function (c) { chunks.push(c); });
886
+ req.on("end", function () {
887
+ lastBody.v = Buffer.concat(chunks).toString("utf8");
888
+ var mode = req.headers["x-scenario"];
889
+ res.writeHead(200, { "Content-Type": "application/json" });
890
+ if (mode === "noid") { res.end(JSON.stringify({ message: "quota exceeded" })); return; }
891
+ if (mode === "badjson") { res.end("<html>not json</html>"); return; }
892
+ res.end(JSON.stringify({ id: "re_abc" }));
893
+ });
894
+ });
895
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
896
+ var port = server.address().port;
897
+ var endpoint = "http://127.0.0.1:" + port + "/emails";
898
+ try {
899
+ var tOk = b.mail.transports.resend({
900
+ apiKey: "re_key", endpoint: endpoint,
901
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
902
+ });
903
+ var rOk = await tOk.send({
904
+ from: "f@x.com", to: "a@x.com", cc: ["c@x.com"], bcc: "d@x.com",
905
+ replyTo: "r@x.com", subject: "s", html: "<p>h</p>", text: "t",
906
+ headers: { "X-H": "1" },
907
+ attachments: [{ filename: "a.txt", content: "hello", contentType: "text/plain", cid: "c1" }],
908
+ });
909
+ check("resend: ok → id", rOk.id === "re_abc");
910
+ var payload = JSON.parse(lastBody.v);
911
+ check("resend: serialize to as array", Array.isArray(payload.to));
912
+ check("resend: serialize cc/bcc arrays", Array.isArray(payload.cc) && Array.isArray(payload.bcc));
913
+ check("resend: serialize reply_to", payload.reply_to === "r@x.com");
914
+ check("resend: serialize attachment base64 + content_id",
915
+ payload.attachments[0].content === Buffer.from("hello").toString("base64") &&
916
+ payload.attachments[0].content_id === "c1");
917
+ } finally {
918
+ await new Promise(function (r) { server.close(function () { r(); }); });
919
+ }
920
+ }
921
+
922
+ async function testResendBadResponseAndNoId() {
923
+ // Dedicated server that keys behavior off the request path so the real
924
+ // resendTransport interpret branches (bad-json + no-id) are exercised.
925
+ var server = http.createServer(function (req, res) {
926
+ res.writeHead(200, { "Content-Type": "application/json" });
927
+ if (req.url.indexOf("badjson") !== -1) { res.end("<html>nope</html>"); return; }
928
+ if (req.url.indexOf("noid") !== -1) { res.end(JSON.stringify({ message: "denied" })); return; }
929
+ res.end(JSON.stringify({ id: "re_ok" }));
930
+ });
931
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
932
+ var port = server.address().port;
933
+ try {
934
+ var tBad = b.mail.transports.resend({
935
+ apiKey: "k", endpoint: "http://127.0.0.1:" + port + "/badjson",
936
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
937
+ });
938
+ var eBad = await _sendErr(tBad, { to: "a@x.com", from: "f@x.com", subject: "s", text: "t" });
939
+ check("resend: non-JSON body → bad-response", eBad && eBad.code === "mail/resend-bad-response");
940
+
941
+ var tNo = b.mail.transports.resend({
942
+ apiKey: "k", endpoint: "http://127.0.0.1:" + port + "/noid",
943
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
944
+ });
945
+ var eNo = await _sendErr(tNo, { to: "a@x.com", from: "f@x.com", subject: "s", text: "t" });
946
+ check("resend: missing id → rejected (reason = message)",
947
+ eNo && eNo.code === "mail/resend-rejected" && /denied/.test(eNo.message));
948
+ } finally {
949
+ await new Promise(function (r) { server.close(function () { r(); }); });
950
+ }
951
+ }
952
+
953
+ // ---------------------------------------------------------------------------
954
+ // create() — opts validation, transport shapes, error wrap, footer/unsub
955
+ // ---------------------------------------------------------------------------
956
+
957
+ function testCreateOptsAndTransportShapes() {
958
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
959
+ check("create: unknown opt rejected",
960
+ threw(function () { b.mail.create({ bogusKey: 1 }); }) !== null);
961
+ check("create: bad transport (object w/o send) rejected",
962
+ threw(function () { b.mail.create({ transport: { name: "x" } }); }).code === "mail/bad-transport");
963
+ // transport as a bare function is wrapped into { send, name:"anonymous" }.
964
+ var m = b.mail.create({ transport: function () { return Promise.resolve({ ok: 1 }); }, audit: false });
965
+ check("create: function transport wrapped", m.transport.name === "anonymous");
966
+ // Default transport (console) when omitted.
967
+ var m2 = b.mail.create({ audit: false });
968
+ check("create: default transport is console", m2.transport.name === "console");
969
+ }
970
+
971
+ async function testCreateTransportErrorWrap() {
972
+ // Non-MailError thrown by transport → wrapped as mail/transport-failed.
973
+ var mPlain = b.mail.create({
974
+ transport: function () { throw new Error("kaboom"); },
975
+ guardDomain: false, audit: false,
976
+ });
977
+ var ePlain = await _sendErr(mPlain, { to: "a@x.com", from: "f@x.com", text: "hi" });
978
+ check("create: plain transport error → mail/transport-failed", ePlain && ePlain.code === "mail/transport-failed");
979
+ check("create: transport error preserves cause", ePlain && ePlain.cause && ePlain.cause.message === "kaboom");
980
+
981
+ // A MailError thrown by transport is re-thrown unchanged.
982
+ var mMail = b.mail.create({
983
+ transport: function () { throw new b.mail.MailError("mail/custom", "nope", true); },
984
+ guardDomain: false, audit: false,
985
+ });
986
+ var eMail = await _sendErr(mMail, { to: "a@x.com", from: "f@x.com", text: "hi" });
987
+ check("create: MailError from transport passes through", eMail && eMail.code === "mail/custom");
988
+ }
989
+
990
+ async function testCreateUnsubscribeExpansion() {
991
+ var captured = [];
992
+ var m = b.mail.create({
993
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
994
+ guardDomain: false, audit: false,
995
+ });
996
+ await m.send({
997
+ to: "a@x.com", from: "f@x.com", text: "hi",
998
+ unsubscribe: { url: "https://x.test/unsub", oneClick: true },
999
+ });
1000
+ var sent = captured[0];
1001
+ var keys = Object.keys(sent.headers || {}).map(function (k) { return k.toLowerCase(); });
1002
+ check("create: unsubscribe expands into List-Unsubscribe header",
1003
+ keys.indexOf("list-unsubscribe") !== -1);
1004
+ check("create: unsubscribe object removed after expansion", sent.unsubscribe === undefined);
1005
+ }
1006
+
1007
+ function testCreateFooterHtmlValidation() {
1008
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
1009
+ var addr = { street: "1 St", city: "X", region: "Y", postalCode: "62701", country: "US" };
1010
+ // Valid override carrying country + postalCode → create succeeds.
1011
+ var ok = threw(function () {
1012
+ b.mail.create({
1013
+ transport: b.mail.transports.memory(), commercial: true, audit: false,
1014
+ postalAddress: addr, footerHtml: "<div>Acme, 62701, US</div>",
1015
+ });
1016
+ });
1017
+ check("create: footerHtml with country+postalCode accepted", ok === null);
1018
+ // Missing country → refused.
1019
+ var eC = threw(function () {
1020
+ b.mail.create({
1021
+ transport: b.mail.transports.memory(), commercial: true, audit: false,
1022
+ postalAddress: addr, footerHtml: "<div>Acme 62701</div>",
1023
+ });
1024
+ });
1025
+ check("create: footerHtml missing country → bad-footer-html", eC && eC.code === "mail/bad-footer-html");
1026
+ // Missing postalCode (country present) → refused.
1027
+ var eP = threw(function () {
1028
+ b.mail.create({
1029
+ transport: b.mail.transports.memory(), commercial: true, audit: false,
1030
+ postalAddress: addr, footerHtml: "<div>Acme US</div>",
1031
+ });
1032
+ });
1033
+ check("create: footerHtml missing postalCode → bad-footer-html", eP && eP.code === "mail/bad-footer-html");
1034
+ }
1035
+
1036
+ async function testCommercialHtmlOnlyAndStringAddress() {
1037
+ // commercial:true with a STRING postalAddress + html-only message: text
1038
+ // is null → the append-when-null branch fills text with the address.
1039
+ var captured = [];
1040
+ var m = b.mail.create({
1041
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1042
+ guardDomain: false, audit: false, commercial: true,
1043
+ postalAddress: "Acme Inc, 500 Rue, 75001 Paris, France",
1044
+ defaults: { from: "shop@x.com" },
1045
+ });
1046
+ await m.send({
1047
+ to: "a@x.com", subject: "promo", html: "<p>buy</p>",
1048
+ headers: { "List-Unsubscribe": "<https://x.test/u>" },
1049
+ });
1050
+ var sent = captured[0];
1051
+ check("commercial: html footer appended with string address", sent.html.indexOf("Acme Inc") !== -1);
1052
+ check("commercial: text synthesized from string address when text null",
1053
+ typeof sent.text === "string" && sent.text.indexOf("Acme Inc") !== -1);
1054
+ }
1055
+
1056
+ // ---------------------------------------------------------------------------
1057
+ // _validateMessage — adversarial / malformed message branches
1058
+ // ---------------------------------------------------------------------------
1059
+
1060
+ function _validator() {
1061
+ return b.mail.create({
1062
+ transport: function () { return Promise.resolve({ ok: 1 }); },
1063
+ guardDomain: false, audit: false,
1064
+ });
1065
+ }
1066
+
1067
+ async function testValidateMessageBranches() {
1068
+ var m = _validator();
1069
+ async function expect(label, message, code) {
1070
+ var e = await _sendErr(m, message);
1071
+ check(label, e && e.code === code);
1072
+ }
1073
+ await expect("missing to", { from: "f@x.com", text: "hi" }, "mail/missing-to");
1074
+ await expect("empty-string recipient", { to: [""], from: "f@x.com", text: "hi" }, "mail/invalid-recipient");
1075
+ await expect("recipient control char", { to: ["a\r\nb@x.com"], from: "f@x.com", text: "hi" }, "mail/invalid-recipient");
1076
+ await expect("recipient bad address", { to: ["notanemail"], from: "f@x.com", text: "hi" }, "mail/invalid-recipient");
1077
+ await expect("missing from", { to: "a@x.com", text: "hi" }, "mail/missing-from");
1078
+ await expect("from control char", { to: "a@x.com", from: "a\r\nb@x.com", text: "hi" }, "mail/invalid-from");
1079
+ await expect("from bad address", { to: "a@x.com", from: "notanemail", text: "hi" }, "mail/invalid-from");
1080
+ await expect("subject CRLF", { to: "a@x.com", from: "f@x.com", subject: "a\r\nb", text: "hi" }, "mail/invalid-subject");
1081
+ await expect("missing body", { to: "a@x.com", from: "f@x.com" }, "mail/missing-body");
1082
+ }
1083
+
1084
+ async function testValidateCalendarBranches() {
1085
+ var m = _validator();
1086
+ async function expect(label, cal, code) {
1087
+ var e = await _sendErr(m, { to: "a@x.com", from: "f@x.com", calendar: cal });
1088
+ check(label, e && e.code === code);
1089
+ }
1090
+ await expect("calendar not object", 5, "mail/invalid-calendar");
1091
+ await expect("calendar bad method", { method: "BOGUS", icalText: "BEGIN:VCALENDAR" }, "mail/invalid-calendar");
1092
+ await expect("calendar no icalText", { method: "REQUEST" }, "mail/invalid-calendar");
1093
+ await expect("calendar not vcalendar", { method: "REQUEST", icalText: "nope" }, "mail/invalid-calendar");
1094
+ // Valid calendar-only message dispatches.
1095
+ var captured = [];
1096
+ var m2 = b.mail.create({
1097
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1098
+ guardDomain: false, audit: false,
1099
+ });
1100
+ await m2.send({ to: "a@x.com", from: "f@x.com", calendar: { method: "PUBLISH", icalText: "BEGIN:VCALENDAR\r\nEND:VCALENDAR" } });
1101
+ check("calendar: valid calendar-only dispatches", captured.length === 1);
1102
+ }
1103
+
1104
+ async function testValidateAttachmentBranches() {
1105
+ var m = _validator();
1106
+ var base = { to: "a@x.com", from: "f@x.com", text: "hi" };
1107
+ async function expect(label, attachments, code) {
1108
+ var msg = Object.assign({}, base, { attachments: attachments });
1109
+ var e = await _sendErr(m, msg);
1110
+ check(label, e && e.code === code);
1111
+ }
1112
+ await expect("attachments not array", "nope", "mail/invalid-attachments");
1113
+ await expect("attachment not object", [5], "mail/invalid-attachment");
1114
+ await expect("attachment no filename", [{ content: "x" }], "mail/invalid-attachment");
1115
+ await expect("attachment filename control char", [{ filename: "a\r\nb.txt", content: "x" }], "mail/invalid-attachment");
1116
+ await expect("attachment filename traversal (guardFilename)", [{ filename: "../../etc/passwd", content: "x" }], "mail/invalid-attachment");
1117
+ await expect("attachment content missing", [{ filename: "a.txt" }], "mail/invalid-attachment");
1118
+ await expect("attachment content wrong type", [{ filename: "a.txt", content: 5 }], "mail/invalid-attachment");
1119
+ await expect("attachment contentType unclean", [{ filename: "a.txt", content: "x", contentType: "text/plain\r\nX: y" }], "mail/invalid-attachment");
1120
+ await expect("attachment bad disposition", [{ filename: "a.txt", content: "x", contentDisposition: "sideways" }], "mail/invalid-attachment");
1121
+ await expect("attachment bad cid (angle bracket)", [{ filename: "a.txt", content: "x", cid: "a<b>" }], "mail/invalid-attachment");
1122
+ // Magic-byte mismatch: real PNG magic bytes but claimed text/plain.
1123
+ await expect("attachment magic-byte mismatch",
1124
+ [{ filename: "a.txt", content: Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D]), contentType: "text/plain" }], // allow:raw-byte-literal — PNG magic bytes vs text/plain claim
1125
+ "mail/invalid-attachment");
1126
+ // skipFilenameSafety + skipMagicByteCheck bypasses → send succeeds.
1127
+ var captured = [];
1128
+ var m2 = b.mail.create({
1129
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1130
+ guardDomain: false, audit: false,
1131
+ });
1132
+ await m2.send(Object.assign({}, base, {
1133
+ attachments: [{
1134
+ filename: "weird name.txt".replace(" ", ""), content: "x",
1135
+ skipFilenameSafety: true, skipMagicByteCheck: true, contentType: "text/plain",
1136
+ contentDisposition: "attachment",
1137
+ }],
1138
+ }));
1139
+ check("attachment: valid attachment dispatches", captured.length === 1);
1140
+ }
1141
+
1142
+ async function testEaiRecipientAccepted() {
1143
+ // EAI path: Unicode local + IDN domain must validate through _isValidEmail.
1144
+ var captured = [];
1145
+ var m = b.mail.create({
1146
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1147
+ guardDomain: false, audit: false,
1148
+ });
1149
+ await m.send({ to: "bü@münchen.de", from: "f@x.com", subject: "grüße", text: "hi" });
1150
+ check("eai: unicode recipient + IDN domain accepted", captured.length === 1);
1151
+ // "Name <addr>" display form accepted.
1152
+ captured.length = 0;
1153
+ await m.send({ to: "Alice <alice@x.com>", from: "Bob <bob@x.com>", text: "hi" });
1154
+ check("display-name form accepted", captured.length === 1);
1155
+ }
1156
+
1157
+ // ---------------------------------------------------------------------------
1158
+ // smtpTransport — config-time gates (no socket needed)
1159
+ // ---------------------------------------------------------------------------
1160
+
1161
+ function testSmtpConfigGates() {
1162
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
1163
+ check("smtp: requires host",
1164
+ threw(function () { b.mail.transports.smtp({}); }).code === "mail/smtp-misconfigured");
1165
+ check("smtp: bad dkimSigner shape",
1166
+ threw(function () { b.mail.transports.smtp({ host: "h", dkimSigner: { nope: 1 } }); }).code === "mail/smtp-misconfigured");
1167
+ check("smtp: CRLF in ehloName refused",
1168
+ threw(function () { b.mail.transports.smtp({ host: "h", ehloName: "x\r\nMAIL FROM:<e>" }); }).code === "mail/smtp-misconfigured");
1169
+ check("smtp: CRLF in user refused",
1170
+ threw(function () { b.mail.transports.smtp({ host: "h", user: "u\r\nx" }); }).code === "mail/smtp-misconfigured");
1171
+ check("smtp: CRLF in pass refused",
1172
+ threw(function () { b.mail.transports.smtp({ host: "h", pass: "p\r\nx" }); }).code === "mail/smtp-misconfigured");
1173
+ check("smtp: CRLF in host refused",
1174
+ threw(function () { b.mail.transports.smtp({ host: "h\r\nx" }); }).code === "mail/smtp-misconfigured");
1175
+ check("smtp: CRLF in servername refused",
1176
+ threw(function () { b.mail.transports.smtp({ host: "h", servername: "s\r\nx" }); }).code === "mail/smtp-misconfigured");
1177
+ check("smtp: bad port refused",
1178
+ threw(function () { b.mail.transports.smtp({ host: "h", port: 70000 }); }).code === "mail/smtp-misconfigured");
1179
+ check("smtp: bad chunkSize refused",
1180
+ threw(function () { b.mail.transports.smtp({ host: "h", chunkSize: -1 }); }).code === "mail/smtp-misconfigured");
1181
+ check("smtp: bad maxTransactionMs refused",
1182
+ threw(function () { b.mail.transports.smtp({ host: "h", maxTransactionMs: -5 }); }).code === "mail/smtp-misconfigured");
1183
+ }
1184
+
1185
+ async function testSmtpDkimSignFailureRejectsPreConnect() {
1186
+ // A dkimSigner whose .sign() throws rejects the send synchronously,
1187
+ // before any socket is opened — so any unroutable host is fine.
1188
+ var t = b.mail.transports.smtp({
1189
+ host: "127.0.0.1", port: 1, implicitTls: true,
1190
+ dkimSigner: { sign: function () { throw new Error("no key"); } },
1191
+ });
1192
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", subject: "s", text: "t" });
1193
+ check("smtp: dkim sign failure → mail/dkim-sign-failed", e && e.code === "mail/dkim-sign-failed");
1194
+ }
1195
+
1196
+ // ---------------------------------------------------------------------------
1197
+ // smtpTransport — full state machine over loopback TLS
1198
+ // ---------------------------------------------------------------------------
1199
+
1200
+ async function testSmtpHappyPathData(certPair) {
1201
+ var st = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1202
+ await listen(st);
1203
+ try {
1204
+ var t = tlsTransport(certPair, st.port);
1205
+ var r = await t.send({ from: "s@a.test", to: ["r1@b.test", "r2@b.test"], cc: "c@b.test", subject: "hi", text: "hello" });
1206
+ check("smtp-data: delivered with code 250", r.transport === "smtp" && r.code === 250);
1207
+ check("smtp-data: sent RCPT for to+cc (3)", st.rcptLines.length === 3);
1208
+ check("smtp-data: used DATA not BDAT", st.bdatChunks.length === 0);
1209
+ } finally { await closeServer(st); }
1210
+ }
1211
+
1212
+ async function testSmtpAuthLogin(certPair) {
1213
+ var st = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1214
+ await listen(st);
1215
+ try {
1216
+ var t = tlsTransport(certPair, st.port, { user: "user", pass: "pass" });
1217
+ var r = await t.send({ from: "s@a.test", to: "r@b.test", subject: "hi", text: "hello" });
1218
+ check("smtp-auth: delivered after AUTH LOGIN", r.code === 250);
1219
+ check("smtp-auth: sent AUTH LOGIN", st.lines.some(function (l) { return /^AUTH LOGIN/i.test(l); }));
1220
+ check("smtp-auth: sent b64 user", st.authLines.indexOf(Buffer.from("user").toString("base64")) !== -1);
1221
+ check("smtp-auth: sent b64 pass", st.authLines.indexOf(Buffer.from("pass").toString("base64")) !== -1);
1222
+ } finally { await closeServer(st); }
1223
+ }
1224
+
1225
+ async function testSmtpBdatChunking(certPair) {
1226
+ var st = startTlsSmtp(certPair, { ext: ["CHUNKING", "8BITMIME"] });
1227
+ await listen(st);
1228
+ try {
1229
+ // Small chunkSize forces multiple BDAT chunks.
1230
+ var t = tlsTransport(certPair, st.port, { chunkSize: C.BYTES.bytes(32) });
1231
+ var r = await t.send({ from: "s@a.test", to: "r@b.test", subject: "hi", text: new Array(20).join("chunkybody-") });
1232
+ check("smtp-bdat: delivered", r.code === 250);
1233
+ check("smtp-bdat: used BDAT (multiple chunks)", st.bdatChunks.length >= 2);
1234
+ check("smtp-bdat: final chunk marked LAST", st.bdatChunks[st.bdatChunks.length - 1].last === true);
1235
+ } finally { await closeServer(st); }
1236
+ }
1237
+
1238
+ async function testSmtpBinaryMimeBdat(certPair) {
1239
+ var st = startTlsSmtp(certPair, { ext: ["CHUNKING", "BINARYMIME", "8BITMIME"] });
1240
+ await listen(st);
1241
+ try {
1242
+ var t = tlsTransport(certPair, st.port);
1243
+ var r = await t.send({
1244
+ from: "s@a.test", to: "r@b.test", subject: "bin", text: "see attached",
1245
+ attachments: [{ filename: "a.bin", content: Buffer.from([0x00, 0x01, 0x02, 0x00]), binary: true, skipMagicByteCheck: true, contentType: "application/octet-stream" }], // allow:raw-byte-literal — NUL-bearing binary fixture
1246
+ });
1247
+ check("smtp-binarymime: delivered via BDAT", r.code === 250 && st.bdatChunks.length >= 1);
1248
+ check("smtp-binarymime: MAIL FROM carried BODY=BINARYMIME", /BODY=BINARYMIME/.test(st.mailFromLine || ""));
1249
+ } finally { await closeServer(st); }
1250
+ }
1251
+
1252
+ async function testSmtpSmtpUtf8And8BitMime(certPair) {
1253
+ var st1 = startTlsSmtp(certPair, { ext: ["SMTPUTF8", "8BITMIME", "SIZE 1000000"] });
1254
+ await listen(st1);
1255
+ try {
1256
+ var t1 = tlsTransport(certPair, st1.port);
1257
+ var r1 = await t1.send({ from: "s@a.test", to: "r@b.test", subject: "grüße", text: "hallo" });
1258
+ check("smtp-utf8: delivered", r1.code === 250);
1259
+ check("smtp-utf8: MAIL FROM carried SMTPUTF8", /SMTPUTF8/.test(st1.mailFromLine || ""));
1260
+ check("smtp-utf8: MAIL FROM carried SIZE=", /SIZE=\d+/.test(st1.mailFromLine || ""));
1261
+ } finally { await closeServer(st1); }
1262
+
1263
+ var st2 = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1264
+ await listen(st2);
1265
+ try {
1266
+ var t2 = tlsTransport(certPair, st2.port);
1267
+ var r2 = await t2.send({ from: "s@a.test", to: "r@b.test", subject: "s", text: "café ☕ body" });
1268
+ check("smtp-8bit: delivered", r2.code === 250);
1269
+ check("smtp-8bit: MAIL FROM carried BODY=8BITMIME", /BODY=8BITMIME/.test(st2.mailFromLine || ""));
1270
+ } finally { await closeServer(st2); }
1271
+ }
1272
+
1273
+ async function testSmtpSizeRefusal(certPair) {
1274
+ var st = startTlsSmtp(certPair, { ext: ["SIZE 10"] });
1275
+ await listen(st);
1276
+ try {
1277
+ var t = tlsTransport(certPair, st.port);
1278
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", subject: "s", text: "this body far exceeds ten bytes" });
1279
+ check("smtp-size: peer SIZE cap exceeded → mail/peer-size-exceeded", e && e.code === "mail/peer-size-exceeded");
1280
+ } finally { await closeServer(st); }
1281
+ }
1282
+
1283
+ async function testSmtpEaiUnsupportedRefusal(certPair) {
1284
+ // Message requires SMTPUTF8 (unicode subject) but peer does not advertise it.
1285
+ var st = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1286
+ await listen(st);
1287
+ try {
1288
+ var t = tlsTransport(certPair, st.port);
1289
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", subject: "grüße", text: "hi" });
1290
+ check("smtp-eai: unicode but no SMTPUTF8 → mail/smtp-failed(eai)",
1291
+ e && e.code === "mail/smtp-failed" && /eai-required-not-supported/.test(e.message));
1292
+ } finally { await closeServer(st); }
1293
+ }
1294
+
1295
+ async function testSmtpBinaryMimeUnsupportedRefusal(certPair) {
1296
+ var st = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1297
+ await listen(st);
1298
+ try {
1299
+ var t = tlsTransport(certPair, st.port);
1300
+ var e = await _sendErr(t, {
1301
+ from: "s@a.test", to: "r@b.test", subject: "s", text: "x",
1302
+ attachments: [{ filename: "a.bin", content: Buffer.from([0x00, 0x00]), binary: true, skipMagicByteCheck: true, contentType: "application/octet-stream" }], // allow:raw-byte-literal — NUL binary fixture
1303
+ });
1304
+ check("smtp-binarymime-unsupported → mail/binarymime-not-advertised",
1305
+ e && e.code === "mail/binarymime-not-advertised");
1306
+ } finally { await closeServer(st); }
1307
+ }
1308
+
1309
+ async function testSmtpRejectionCodes(certPair) {
1310
+ // greeting rejected
1311
+ var stG = startTlsSmtp(certPair, { greeting: "554 go away" });
1312
+ await listen(stG);
1313
+ try {
1314
+ var eG = await _sendErr(tlsTransport(certPair, stG.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1315
+ check("smtp-reject: greeting 554 → smtp-failed", eG && /greeting-rejected/.test(eG.message));
1316
+ } finally { await closeServer(stG); }
1317
+
1318
+ // EHLO rejected
1319
+ var stE = startTlsSmtp(certPair, { ext: [], ehloCode: 500 });
1320
+ await listen(stE);
1321
+ try {
1322
+ var eE = await _sendErr(tlsTransport(certPair, stE.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1323
+ check("smtp-reject: EHLO 500 → smtp-failed", eE && /ehlo-rejected/.test(eE.message));
1324
+ } finally { await closeServer(stE); }
1325
+
1326
+ // MAIL FROM rejected
1327
+ var stM = startTlsSmtp(certPair, { ext: ["8BITMIME"], mailFromCode: 550 });
1328
+ await listen(stM);
1329
+ try {
1330
+ var eM = await _sendErr(tlsTransport(certPair, stM.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1331
+ check("smtp-reject: MAIL FROM 550 → smtp-failed", eM && /mail-from-rejected/.test(eM.message));
1332
+ } finally { await closeServer(stM); }
1333
+
1334
+ // RCPT rejected
1335
+ var stR = startTlsSmtp(certPair, { ext: ["8BITMIME"], rcptCode: 550 });
1336
+ await listen(stR);
1337
+ try {
1338
+ var eR = await _sendErr(tlsTransport(certPair, stR.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1339
+ check("smtp-reject: RCPT 550 → smtp-failed", eR && /rcpt-rejected/.test(eR.message));
1340
+ } finally { await closeServer(stR); }
1341
+
1342
+ // DATA rejected
1343
+ var stD = startTlsSmtp(certPair, { ext: ["8BITMIME"], dataCode: 503 });
1344
+ await listen(stD);
1345
+ try {
1346
+ var eD = await _sendErr(tlsTransport(certPair, stD.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1347
+ check("smtp-reject: DATA 503 → smtp-failed", eD && /data-rejected/.test(eD.message));
1348
+ } finally { await closeServer(stD); }
1349
+
1350
+ // BODY rejected (post-DATA final code non-250 → smtp-rejected)
1351
+ var stB = startTlsSmtp(certPair, { ext: ["8BITMIME"], bodyCode: 552 });
1352
+ await listen(stB);
1353
+ try {
1354
+ var eB = await _sendErr(tlsTransport(certPair, stB.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1355
+ check("smtp-reject: BODY 552 → mail/smtp-rejected", eB && eB.code === "mail/smtp-rejected");
1356
+ } finally { await closeServer(stB); }
1357
+
1358
+ // BDAT chunk rejected
1359
+ var stBd = startTlsSmtp(certPair, { ext: ["CHUNKING", "8BITMIME"], bdatCode: 552 });
1360
+ await listen(stBd);
1361
+ try {
1362
+ var eBd = await _sendErr(tlsTransport(certPair, stBd.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1363
+ check("smtp-reject: BDAT chunk 552 → mail/bdat-chunk-rejected", eBd && eBd.code === "mail/bdat-chunk-rejected");
1364
+ } finally { await closeServer(stBd); }
1365
+
1366
+ // AUTH final rejected
1367
+ var stA = startTlsSmtp(certPair, { ext: ["8BITMIME"], authFinalCode: 535 });
1368
+ await listen(stA);
1369
+ try {
1370
+ var eA = await _sendErr(tlsTransport(certPair, stA.port, { user: "u", pass: "p" }), { from: "s@a.test", to: "r@b.test", text: "x" });
1371
+ check("smtp-reject: AUTH final 535 → smtp-failed", eA && /auth-failed/.test(eA.message));
1372
+ } finally { await closeServer(stA); }
1373
+ }
1374
+
1375
+ async function testSmtpResponseTooLarge(certPair) {
1376
+ var st = startTlsSmtp(certPair, { greetingFlood: C.BYTES.kib(300) });
1377
+ await listen(st);
1378
+ try {
1379
+ var e = await _sendErr(tlsTransport(certPair, st.port), { from: "s@a.test", to: "r@b.test", text: "x" });
1380
+ check("smtp: oversize framing → response-too-large", e && /response-too-large/.test(e.message));
1381
+ } finally { await closeServer(st); }
1382
+ }
1383
+
1384
+ async function testSmtpTransactionTimeout(certPair) {
1385
+ // Server greets then never answers EHLO; a small maxTransactionMs
1386
+ // trips the absolute transaction deadline (socket idle timeout kept high).
1387
+ var st = startTlsSmtp(certPair, { noEhloResponse: true });
1388
+ await listen(st);
1389
+ try {
1390
+ var t = tlsTransport(certPair, st.port, { maxTransactionMs: C.TIME.seconds(0.3), timeoutMs: C.TIME.seconds(10) });
1391
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", text: "x" });
1392
+ check("smtp: transaction deadline → transaction-timeout", e && /transaction-timeout/.test(e.message));
1393
+ } finally { await closeServer(st); }
1394
+ }
1395
+
1396
+ async function testSmtpSocketTimeout(certPair) {
1397
+ // Small socket idle timeout, large transaction deadline → 'timeout' fires.
1398
+ var st = startTlsSmtp(certPair, { noEhloResponse: true });
1399
+ await listen(st);
1400
+ try {
1401
+ var t = tlsTransport(certPair, st.port, { timeoutMs: C.TIME.seconds(0.3), maxTransactionMs: C.TIME.seconds(30) });
1402
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", text: "x" });
1403
+ check("smtp: idle timeout → smtp-failed(timeout)", e && e.code === "mail/smtp-failed" && /timeout/.test(e.message));
1404
+ } finally { await closeServer(st); }
1405
+ }
1406
+
1407
+ async function testSmtpStarttlsHappyPath(certPair) {
1408
+ var st = startStarttlsSmtp(certPair, { ext: ["8BITMIME"] });
1409
+ await listen(st);
1410
+ try {
1411
+ var t = starttlsTransport(certPair, st.port);
1412
+ var r = await t.send({ from: "s@a.test", to: "r@b.test", subject: "hi", text: "hello" });
1413
+ check("smtp-starttls: delivered after upgrade", r.code === 250);
1414
+ check("smtp-starttls: issued STARTTLS then re-EHLO",
1415
+ st.lines.filter(function (l) { return /^EHLO/i.test(l); }).length >= 2);
1416
+ } finally { await closeServer(st); }
1417
+ }
1418
+
1419
+ async function testSmtpStarttlsRejected(certPair) {
1420
+ var st = startStarttlsSmtp(certPair, { starttls: "reject" });
1421
+ await listen(st);
1422
+ try {
1423
+ var t = starttlsTransport(certPair, st.port);
1424
+ var e = await _sendErr(t, { from: "s@a.test", to: "r@b.test", text: "x" });
1425
+ check("smtp-starttls: rejected upgrade → starttls-rejected", e && /starttls-rejected/.test(e.message));
1426
+ } finally { await closeServer(st); }
1427
+ }
1428
+
1429
+ // ---------------------------------------------------------------------------
1430
+ // reverseDns — error branches via a fake resolver injected through
1431
+ // network-dns? Not injectable; use documented shape checks on bad input.
1432
+ // ---------------------------------------------------------------------------
1433
+
1434
+ async function testReverseDnsErrorShape() {
1435
+ var r = await b.mail.reverseDns("definitely not an ip");
1436
+ check("reverseDns: bad input → ok:false + error", r.ok === false && typeof r.error === "string" && r.fcrdns === false);
1437
+ }
1438
+
1439
+ // ---------------------------------------------------------------------------
1440
+ // b.mail.feedbackId — Gmail FBL 4-tuple builder: every reject branch + ok
1441
+ // ---------------------------------------------------------------------------
1442
+
1443
+ function testFeedbackId() {
1444
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
1445
+ var ok = b.mail.feedbackId({
1446
+ campaignId: "wk26-promo", customerId: "acme", mailType: "marketing", senderId: "pool-1",
1447
+ });
1448
+ check("feedbackId: valid 4-tuple joins with ':'", ok === "wk26-promo:acme:marketing:pool-1");
1449
+
1450
+ check("feedbackId: no opts → bad-feedback-id-opts",
1451
+ threw(function () { return b.mail.feedbackId(); }).code === "mail/bad-feedback-id-opts");
1452
+ check("feedbackId: non-object opts → bad-feedback-id-opts",
1453
+ threw(function () { return b.mail.feedbackId("nope"); }).code === "mail/bad-feedback-id-opts");
1454
+ check("feedbackId: missing field → bad-feedback-id-field",
1455
+ threw(function () { return b.mail.feedbackId({ customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1456
+ check("feedbackId: empty-string field → bad-feedback-id-field",
1457
+ threw(function () { return b.mail.feedbackId({ campaignId: "", customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1458
+ check("feedbackId: >64-char field → bad-feedback-id-field",
1459
+ threw(function () { return b.mail.feedbackId({ campaignId: "x".repeat(65), customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1460
+ check("feedbackId: ':' in field (separator corruption) → bad-feedback-id-field",
1461
+ threw(function () { return b.mail.feedbackId({ campaignId: "a:b", customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1462
+ check("feedbackId: control char in field → bad-feedback-id-field",
1463
+ threw(function () { return b.mail.feedbackId({ campaignId: "a\tb", customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1464
+ check("feedbackId: CR in field → bad-feedback-id-field",
1465
+ threw(function () { return b.mail.feedbackId({ campaignId: "a\rb", customerId: "a", mailType: "b", senderId: "c" }); }).code === "mail/bad-feedback-id-field");
1466
+ }
1467
+
1468
+ // ---------------------------------------------------------------------------
1469
+ // b.mail.toAscii — domainToASCII returns empty (non-delimiter junk) → null
1470
+ // ---------------------------------------------------------------------------
1471
+
1472
+ function testToAsciiPunycodeEmptyReturnsNull() {
1473
+ // A bare space / bare "xn--" prefix reaches nodeUrl.domainToASCII (no URL
1474
+ // delimiter to short-circuit on) which yields "" — the length-0 guard maps
1475
+ // that to null rather than surfacing an empty ACE label.
1476
+ check("toAscii(' ') → null (empty ACE)", b.mail.toAscii(" ") === null);
1477
+ check("toAscii('xn--') → null (empty ACE)", b.mail.toAscii("xn--") === null);
1478
+ }
1479
+
1480
+ // ---------------------------------------------------------------------------
1481
+ // b.mail.create — CAN-SPAM postalAddress validation at construction time
1482
+ // ---------------------------------------------------------------------------
1483
+
1484
+ function testCreateCommercialPostalAddressValidation() {
1485
+ function threw(fn) { try { fn(); return null; } catch (e) { return e; } }
1486
+ var mem = b.mail.transports.memory();
1487
+ var validAddr = { street: "1 St", city: "Springfield", region: "IL", postalCode: "62701", country: "US" };
1488
+
1489
+ check("create: commercial:true with no postalAddress → missing-postal-address",
1490
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, audit: false }); }).code === "mail/missing-postal-address");
1491
+ check("create: commercial:true empty-string address → missing-postal-address",
1492
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, postalAddress: " ", audit: false }); }).code === "mail/missing-postal-address");
1493
+ check("create: commercial:true non-object non-string address → missing-postal-address",
1494
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, postalAddress: 5, audit: false }); }).code === "mail/missing-postal-address");
1495
+ check("create: commercial:true object address missing a required field → missing-postal-address",
1496
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, audit: false,
1497
+ postalAddress: { street: "1 St", city: "X", region: "Y", postalCode: "62701" } }); }).code === "mail/missing-postal-address");
1498
+ check("create: commercial:true address field with control char → missing-postal-address",
1499
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, audit: false,
1500
+ postalAddress: { street: "1 St", city: "X", region: "Y", postalCode: "62701", country: "U\nS" } }); }).code === "mail/missing-postal-address");
1501
+
1502
+ // regulated:true is the alias for commercial:true — same postal-address gate.
1503
+ check("create: regulated:true is the commercial alias (valid address builds)",
1504
+ threw(function () { return b.mail.create({ transport: mem, regulated: true, postalAddress: validAddr, audit: false }); }) === null);
1505
+ check("create: regulated:true with no address → missing-postal-address",
1506
+ threw(function () { return b.mail.create({ transport: mem, regulated: true, audit: false }); }).code === "mail/missing-postal-address");
1507
+
1508
+ // String-shape postalAddress carries no structured country/postalCode fields,
1509
+ // so a footerHtml override needn't (can't) echo them — create still builds.
1510
+ check("create: string postalAddress + footerHtml builds (no structured field check)",
1511
+ threw(function () { return b.mail.create({ transport: mem, commercial: true, audit: false,
1512
+ postalAddress: "Acme, 500 Rue, 75001 Paris, FR", footerHtml: "<div>anything</div>" }); }) === null);
1513
+ }
1514
+
1515
+ // ---------------------------------------------------------------------------
1516
+ // b.mail.create send() — CAN-SPAM opt-out enforcement + object-address footer
1517
+ // ---------------------------------------------------------------------------
1518
+
1519
+ async function testCommercialSendCanSpamAndObjectFooter() {
1520
+ var addr = { street: "1 St", city: "Springfield", region: "IL", postalCode: "62701", country: "US" };
1521
+ var captured = [];
1522
+ var m = b.mail.create({
1523
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1524
+ guardDomain: false, audit: false, commercial: true, postalAddress: addr,
1525
+ });
1526
+
1527
+ // (a) commercial send with neither unsubscribe object nor List-Unsubscribe
1528
+ // header → hard refusal before the transport is touched.
1529
+ var eNoUnsub = await _sendErr(m, { to: "a@x.com", from: "f@x.com", text: "hi" });
1530
+ check("commercial send: no opt-out → mail/canspam-no-unsubscribe",
1531
+ eNoUnsub && eNoUnsub.code === "mail/canspam-no-unsubscribe");
1532
+ check("commercial send: refusal happened before transport", captured.length === 0);
1533
+
1534
+ // Headers present but none is List-Unsubscribe → the header scan exhausts
1535
+ // and the opt-out gate still refuses (distinct branch from headers absent).
1536
+ var eOtherHdr = await _sendErr(m, { to: "a@x.com", from: "f@x.com", text: "hi", headers: { "X-Other": "1" } });
1537
+ check("commercial send: non-matching headers still → mail/canspam-no-unsubscribe",
1538
+ eOtherHdr && eOtherHdr.code === "mail/canspam-no-unsubscribe");
1539
+
1540
+ // (b) unsubscribe object → expands to List-Unsubscribe, send proceeds, and
1541
+ // the html footer is rendered from the STRUCTURED object address (<br>-joined
1542
+ // street / city+region+postal / country).
1543
+ captured.length = 0;
1544
+ await m.send({ to: "a@x.com", from: "f@x.com", html: "<p>buy</p>", unsubscribe: { url: "https://x.test/u" } });
1545
+ var sHtml = captured[0];
1546
+ check("commercial send: object address html footer carries street", sHtml.html.indexOf("1 St") !== -1);
1547
+ check("commercial send: object address html footer carries country", sHtml.html.indexOf("US") !== -1);
1548
+ check("commercial send: object address html footer uses <br> join", sHtml.html.indexOf("<br>") !== -1);
1549
+ check("commercial send: text part synthesized from object address when html-only",
1550
+ typeof sHtml.text === "string" && sHtml.text.indexOf("Springfield") !== -1);
1551
+
1552
+ // (c) a pre-existing List-Unsubscribe header satisfies the opt-out gate too
1553
+ // (the header-scan branch of _hasUnsubscribe, distinct from the object form).
1554
+ captured.length = 0;
1555
+ await m.send({ to: "a@x.com", from: "f@x.com", text: "hi", headers: { "List-Unsubscribe": "<https://x.test/u>" } });
1556
+ check("commercial send: List-Unsubscribe header satisfies opt-out gate", captured.length === 1);
1557
+ }
1558
+
1559
+ // ---------------------------------------------------------------------------
1560
+ // _validateMessage — Reply-To / custom-header CRLF+NUL injection refusals
1561
+ // ---------------------------------------------------------------------------
1562
+
1563
+ async function testMessageHeaderInjectionBranches() {
1564
+ var m = b.mail.create({
1565
+ transport: function () { return Promise.resolve({ ok: 1 }); },
1566
+ guardDomain: false, audit: false,
1567
+ });
1568
+ var base = { to: "a@x.com", from: "f@x.com", text: "hi" };
1569
+ function withProp(extra) { return Object.assign({}, base, extra); }
1570
+
1571
+ var eReply = await _sendErr(m, withProp({ replyTo: "a\r\nBcc: evil@x.com" }));
1572
+ check("send: CRLF in replyTo → mail/invalid-reply-to (header injection)",
1573
+ eReply && eReply.code === "mail/invalid-reply-to");
1574
+
1575
+ var crlfKey = {}; crlfKey["X-Evil\r\nBcc"] = "evil@x.com";
1576
+ var eKeyCrlf = await _sendErr(m, withProp({ headers: crlfKey }));
1577
+ check("send: CRLF in a header key → mail/invalid-header",
1578
+ eKeyCrlf && eKeyCrlf.code === "mail/invalid-header");
1579
+
1580
+ var nulKey = {}; nulKey["X-Evil\0Bcc"] = "evil@x.com";
1581
+ var eKeyNul = await _sendErr(m, withProp({ headers: nulKey }));
1582
+ check("send: NUL in a header key → mail/invalid-header",
1583
+ eKeyNul && eKeyNul.code === "mail/invalid-header");
1584
+
1585
+ var eValCrlf = await _sendErr(m, withProp({ headers: { "X-Good": "ok\r\nBcc: evil@x.com" } }));
1586
+ check("send: CRLF in a header value → mail/invalid-header",
1587
+ eValCrlf && eValCrlf.code === "mail/invalid-header");
1588
+ }
1589
+
1590
+ // ---------------------------------------------------------------------------
1591
+ // _mergeMessage — defaults.headers ∪ message.headers shallow merge
1592
+ // ---------------------------------------------------------------------------
1593
+
1594
+ async function testMergeMessageHeaderMerge() {
1595
+ var captured = [];
1596
+ var m = b.mail.create({
1597
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1598
+ guardDomain: false, audit: false,
1599
+ defaults: { headers: { "X-Default": "d" } },
1600
+ });
1601
+ await m.send({ to: "a@x.com", from: "f@x.com", text: "hi", headers: { "X-Msg": "mm" } });
1602
+ var hk = Object.keys(captured[0].headers || {});
1603
+ check("merge: defaults.headers + message.headers both present",
1604
+ hk.indexOf("X-Default") !== -1 && hk.indexOf("X-Msg") !== -1);
1605
+ }
1606
+
1607
+ // ---------------------------------------------------------------------------
1608
+ // smtpTransport — rejectUnauthorized:false audits the insecure TLS session
1609
+ // ---------------------------------------------------------------------------
1610
+
1611
+ function testSmtpRejectUnauthorizedAudits() {
1612
+ // Building the transport with cert-verification disabled must audit the
1613
+ // insecure-TLS decision at config time (no socket, no throw) — the audit
1614
+ // sink drops silently, so we only assert the transport still constructs.
1615
+ var t = b.mail.transports.smtp({ host: "mx.example.test", rejectUnauthorized: false });
1616
+ check("smtp: rejectUnauthorized:false still builds a transport", typeof t.send === "function");
1617
+ }
1618
+
1619
+ // ---------------------------------------------------------------------------
1620
+ // SMTP wire — BINARYMIME auto-detected from a NUL-bearing Buffer attachment
1621
+ // even without an explicit binary:true marker.
1622
+ // ---------------------------------------------------------------------------
1623
+
1624
+ async function testSmtpBinaryMimeFromNulBuffer(certPair) {
1625
+ var st = startTlsSmtp(certPair, { ext: ["CHUNKING", "BINARYMIME", "8BITMIME"] });
1626
+ await listen(st);
1627
+ try {
1628
+ var t = tlsTransport(certPair, st.port);
1629
+ var r = await t.send({
1630
+ from: "s@a.test", to: "r@b.test", subject: "bin", text: "see attached",
1631
+ // NUL byte in the octet stream, but NO binary:true flag — the transport's
1632
+ // first-4-KiB scan must classify this as binary and emit BODY=BINARYMIME.
1633
+ attachments: [{
1634
+ filename: "a.dat", content: Buffer.from([0x41, 0x00, 0x42, 0x00]), // allow:raw-byte-literal — NUL-bearing octet stream, no binary flag
1635
+ skipMagicByteCheck: true, contentType: "application/octet-stream",
1636
+ }],
1637
+ });
1638
+ check("smtp-binarymime-autodetect: delivered via BDAT", r.code === 250 && st.bdatChunks.length >= 1);
1639
+ check("smtp-binarymime-autodetect: MAIL FROM carried BODY=BINARYMIME (no binary flag)",
1640
+ /BODY=BINARYMIME/.test(st.mailFromLine || ""));
1641
+ } finally { await closeServer(st); }
1642
+
1643
+ // Counterpart: a Buffer attachment with NO NUL bytes and no binary flag must
1644
+ // NOT be classified binary — the scan exhausts and MAIL FROM omits BINARYMIME.
1645
+ var st2 = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1646
+ await listen(st2);
1647
+ try {
1648
+ var t2 = tlsTransport(certPair, st2.port);
1649
+ var r2 = await t2.send({
1650
+ from: "s@a.test", to: "r@b.test", subject: "clean", text: "hi",
1651
+ attachments: [{
1652
+ filename: "note.txt", content: Buffer.from("plain ascii payload", "utf8"),
1653
+ skipMagicByteCheck: true, contentType: "text/plain",
1654
+ }],
1655
+ });
1656
+ check("smtp-binarymime-autodetect: clean buffer delivered", r2.code === 250);
1657
+ check("smtp-binarymime-autodetect: clean buffer omits BODY=BINARYMIME",
1658
+ !/BODY=BINARYMIME/.test(st2.mailFromLine || ""));
1659
+ } finally { await closeServer(st2); }
1660
+ }
1661
+
1662
+ // ---------------------------------------------------------------------------
1663
+ // _isValidEmail — EAI / length edge branches driven through the send path
1664
+ // (guardDomain:false isolates the recipient-shape validator).
1665
+ // ---------------------------------------------------------------------------
1666
+
1667
+ async function testEaiAndLengthValidationEdges() {
1668
+ var captured = [];
1669
+ var m = b.mail.create({
1670
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1671
+ guardDomain: false, audit: false,
1672
+ });
1673
+ async function reject(label, to, code) {
1674
+ var e = await _sendErr(m, { to: to, from: "f@x.com", text: "hi" });
1675
+ check(label, e && e.code === code);
1676
+ }
1677
+ // EAI local part empty (atIdx <= 0) — non-ASCII forces the EAI branch.
1678
+ await reject("eai: '@münchen.de' (empty local) → invalid-recipient",
1679
+ "@münchen.de", "mail/invalid-recipient");
1680
+ // EAI trailing '@' (atIdx === length-1).
1681
+ await reject("eai: 'bü@' (trailing @) → invalid-recipient",
1682
+ "bü@", "mail/invalid-recipient");
1683
+ // IDN domain with NO dot: punycodes to a label the ASCII EMAIL_RE rejects
1684
+ // (needs a '.'), so the re-test of the ACE domain fails closed.
1685
+ await reject("eai: IDN domain without a dot → invalid-recipient",
1686
+ "bü@münchen", "mail/invalid-recipient");
1687
+ // Pure-ASCII address beyond the RFC 5321 §4.5.3.1.3 254-octet forward-path
1688
+ // bound is refused before the regex ever runs.
1689
+ await reject("eai: >254-octet ASCII address → invalid-recipient",
1690
+ "x".repeat(250) + "@x.com", "mail/invalid-recipient");
1691
+
1692
+ // Counterpart: a well-formed EAI address (unicode local + IDN domain that
1693
+ // punycodes to a dotted ACE form) is accepted through the same path.
1694
+ captured.length = 0;
1695
+ await m.send({ to: "bü@münchen.de", from: "f@x.com", text: "hi" });
1696
+ check("eai: unicode-local + dotted IDN domain accepted", captured.length === 1);
1697
+ }
1698
+
1699
+ // ---------------------------------------------------------------------------
1700
+ // create() — guardDomain profile fallback chain + custom footerSeparator
1701
+ // ---------------------------------------------------------------------------
1702
+
1703
+ async function testCreateGuardDomainProfileFallback() {
1704
+ // guardDomain:{} (object, no .profile) falls back to opts.profile.
1705
+ var m1 = b.mail.create({
1706
+ transport: b.mail.transports.memory(), guardDomain: {}, profile: "permissive",
1707
+ });
1708
+ var r1 = await m1.send({ to: "a@192.168.1.1", from: "s@example.org", subject: "t", text: "hi" });
1709
+ check("create: guardDomain:{} + profile:'permissive' allows bare-IP (opts.profile fallback)",
1710
+ r1 && r1.transport === "memory");
1711
+
1712
+ // No guardDomain opt at all → opts.profile drives the default-on gate.
1713
+ var m2 = b.mail.create({ transport: b.mail.transports.memory(), profile: "permissive" });
1714
+ var r2 = await m2.send({ to: "a@192.168.1.1", from: "s@example.org", subject: "t", text: "hi" });
1715
+ check("create: bare profile:'permissive' (no guardDomain opt) allows bare-IP",
1716
+ r2 && r2.transport === "memory");
1717
+ }
1718
+
1719
+ async function testCommercialCustomFooterSeparator() {
1720
+ // footerSeparator overrides the default "\n\n----\n" / "<hr>" on both parts.
1721
+ var addr = { street: "1 St", city: "Springfield", region: "IL", postalCode: "62701", country: "US" };
1722
+ var captured = [];
1723
+ var m = b.mail.create({
1724
+ transport: function (msg) { captured.push(msg); return Promise.resolve({ ok: 1 }); },
1725
+ guardDomain: false, audit: false, commercial: true, postalAddress: addr,
1726
+ footerSeparator: "\n==SEP==\n",
1727
+ });
1728
+ await m.send({
1729
+ to: "a@x.com", from: "f@x.com", text: "body", html: "<p>body</p>",
1730
+ unsubscribe: { url: "https://x.test/u" },
1731
+ });
1732
+ var sent = captured[0];
1733
+ check("commercial: custom footerSeparator applied to text part",
1734
+ sent.text.indexOf("\n==SEP==\n") !== -1);
1735
+ check("commercial: custom footerSeparator applied to html part",
1736
+ sent.html.indexOf("\n==SEP==\n") !== -1);
1737
+ }
1738
+
1739
+ // ---------------------------------------------------------------------------
1740
+ // consoleTransport — single (non-array) cc branch
1741
+ // ---------------------------------------------------------------------------
1742
+
1743
+ async function testConsoleSingleCc() {
1744
+ var s = _fakeStream();
1745
+ var t = b.mail.transports.console({ stream: s });
1746
+ await t.send({ to: "solo@x.com", from: "f@x.com", cc: "one-cc@x.com", text: "hi" });
1747
+ check("console: single (non-array) cc printed verbatim",
1748
+ s.data.indexOf("Cc: one-cc@x.com") !== -1);
1749
+ }
1750
+
1751
+ // ---------------------------------------------------------------------------
1752
+ // httpTransport — custom method + verdict-ok-without-id branch
1753
+ // ---------------------------------------------------------------------------
1754
+
1755
+ async function testHttpMethodAndVerdictNoId() {
1756
+ var seenMethod = { v: null };
1757
+ var server = http.createServer(function (req, res) {
1758
+ seenMethod.v = req.method;
1759
+ var chunks = [];
1760
+ req.on("data", function (c) { chunks.push(c); });
1761
+ req.on("end", function () {
1762
+ res.writeHead(200, { "Content-Type": "application/json" });
1763
+ res.end(JSON.stringify({ ok: true }));
1764
+ });
1765
+ });
1766
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
1767
+ var port = server.address().port;
1768
+ var base = {
1769
+ endpoint: "http://127.0.0.1:" + port + "/send", name: "vend",
1770
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
1771
+ };
1772
+ try {
1773
+ // Custom method (PUT) reaches the wire.
1774
+ var tPut = b.mail.transports.http(Object.assign({}, base, {
1775
+ method: "put",
1776
+ serialize: function () { return { body: "{}" }; },
1777
+ }));
1778
+ var rPut = await tPut.send({ to: "a@x.com", from: "f@x.com" });
1779
+ check("http: custom method upper-cased + sent (PUT)", seenMethod.v === "PUT" && rPut.transport === "vend");
1780
+
1781
+ // interpret returns a truthy verdict with ok:true but no id / no extra →
1782
+ // info returned unchanged (neither id nor extra merged).
1783
+ var tOk = b.mail.transports.http(Object.assign({}, base, {
1784
+ serialize: function () { return { body: "{}" }; },
1785
+ interpret: function () { return { ok: true }; },
1786
+ }));
1787
+ var rOk = await tOk.send({ to: "a@x.com", from: "f@x.com" });
1788
+ check("http: verdict ok w/o id → info has no id", rOk.id === undefined && rOk.statusCode === 200);
1789
+ } finally {
1790
+ await new Promise(function (r) { server.close(function () { r(); }); });
1791
+ }
1792
+ }
1793
+
1794
+ // ---------------------------------------------------------------------------
1795
+ // resendTransport — minimal serialize (no optional fields) + no-id/no-message
1796
+ // rejection (reason falls back to JSON.stringify(data))
1797
+ // ---------------------------------------------------------------------------
1798
+
1799
+ async function testResendMinimalAndNoMessageReason() {
1800
+ var lastBody = { v: null };
1801
+ var server = http.createServer(function (req, res) {
1802
+ var chunks = [];
1803
+ req.on("data", function (c) { chunks.push(c); });
1804
+ req.on("end", function () {
1805
+ lastBody.v = Buffer.concat(chunks).toString("utf8");
1806
+ res.writeHead(200, { "Content-Type": "application/json" });
1807
+ if (req.url.indexOf("emptyobj") !== -1) { res.end("{}"); return; }
1808
+ res.end(JSON.stringify({ id: "re_min" }));
1809
+ });
1810
+ });
1811
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
1812
+ var port = server.address().port;
1813
+ try {
1814
+ // Minimal message: only from/to → serialize omits cc/bcc/replyTo/html/
1815
+ // attachments (all the false branches) and subject defaults to "".
1816
+ var tMin = b.mail.transports.resend({
1817
+ apiKey: "k", endpoint: "http://127.0.0.1:" + port + "/emails",
1818
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
1819
+ });
1820
+ var rMin = await tMin.send({ from: "f@x.com", to: "a@x.com" });
1821
+ check("resend: minimal send → id", rMin.id === "re_min");
1822
+ var payload = JSON.parse(lastBody.v);
1823
+ check("resend: minimal serialize omits cc/bcc/replyTo/html/attachments",
1824
+ payload.cc === undefined && payload.bcc === undefined && payload.reply_to === undefined &&
1825
+ payload.html === undefined && payload.attachments === undefined && payload.subject === "");
1826
+
1827
+ // Response {} → no id AND no message field → reason = JSON.stringify(data).
1828
+ var tEmpty = b.mail.transports.resend({
1829
+ apiKey: "k", endpoint: "http://127.0.0.1:" + port + "/emptyobj",
1830
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
1831
+ });
1832
+ var eEmpty = await _sendErr(tEmpty, { from: "f@x.com", to: "a@x.com" });
1833
+ check("resend: no-id no-message body → rejected with JSON.stringify reason",
1834
+ eEmpty && eEmpty.code === "mail/resend-rejected" && /\{\}/.test(eEmpty.message));
1835
+ } finally {
1836
+ await new Promise(function (r) { server.close(function () { r(); }); });
1837
+ }
1838
+ }
1839
+
1840
+ // ---------------------------------------------------------------------------
1841
+ // smtpTransport — config-time option permutations (no socket)
1842
+ // ---------------------------------------------------------------------------
1843
+
1844
+ function testSmtpOptionPermutations() {
1845
+ // implicitTls:true on a non-465 port, plus TLS-shape opts (ecdhCurve / ca /
1846
+ // minTlsVersion) and a forced IPv6 family preference — all wired at build.
1847
+ var t1 = b.mail.transports.smtp({
1848
+ host: "mx.example.test", port: 2525, implicitTls: true,
1849
+ ecdhCurve: "X25519MLKEM768", ca: "-----BEGIN CERTIFICATE-----\nx\n-----END CERTIFICATE-----",
1850
+ minTlsVersion: "TLSv1.3", preferFamily: "6",
1851
+ });
1852
+ check("smtp: implicitTls + ecdhCurve/ca/minTlsVersion + preferFamily:'6' builds",
1853
+ typeof t1.send === "function");
1854
+
1855
+ // Bare-IP host → SNI servername must be omitted (Node rejects SNI to an IP);
1856
+ // the transport still constructs.
1857
+ var t2 = b.mail.transports.smtp({ host: "127.0.0.1", port: 587 });
1858
+ check("smtp: bare-IPv4 host builds (servername auto-omitted)", typeof t2.send === "function");
1859
+
1860
+ // Colon-bearing (IPv6-shaped) host also omits SNI.
1861
+ var t3 = b.mail.transports.smtp({ host: "2001:db8::1", port: 587 });
1862
+ check("smtp: IPv6-shaped host builds (servername auto-omitted)", typeof t3.send === "function");
1863
+
1864
+ // Explicit chunkSize:0 is a positive-finite-int violation → refused.
1865
+ var threw = null;
1866
+ try { b.mail.transports.smtp({ host: "h", chunkSize: 0 }); } catch (e) { threw = e; }
1867
+ check("smtp: chunkSize:0 refused (positive-finite-int gate)",
1868
+ threw && threw.code === "mail/smtp-misconfigured");
1869
+ }
1870
+
1871
+ // ---------------------------------------------------------------------------
1872
+ // SMTP wire — _parsePeerSize no-cap ('SIZE' alone) + junk-arg ('SIZE abc')
1873
+ // ---------------------------------------------------------------------------
1874
+
1875
+ async function testSmtpParsePeerSizeNoCapAndJunk(certPair) {
1876
+ // 'SIZE' advertised with no argument → peerSizeCap 0 (no enforced cap): the
1877
+ // pre-check is skipped BUT MAIL FROM still carries SIZE= (peerSizeCap !== -1).
1878
+ var stNoCap = startTlsSmtp(certPair, { ext: ["8BITMIME", "SIZE"] });
1879
+ await listen(stNoCap);
1880
+ try {
1881
+ var t = tlsTransport(certPair, stNoCap.port);
1882
+ var r = await t.send({ from: "s@a.test", to: "r@b.test", subject: "s", text: "body bytes" });
1883
+ check("smtp-size-nocap: delivered (no enforced cap)", r.code === 250);
1884
+ check("smtp-size-nocap: MAIL FROM still carries SIZE=", /SIZE=\d+/.test(stNoCap.mailFromLine || ""));
1885
+ } finally { await closeServer(stNoCap); }
1886
+
1887
+ // 'SIZE abc' (non-numeric argument) → peerSizeCap -1 (treated as unadvertised):
1888
+ // no pre-check and NO SIZE= keyword on MAIL FROM.
1889
+ var stJunk = startTlsSmtp(certPair, { ext: ["8BITMIME", "SIZE abc"] });
1890
+ await listen(stJunk);
1891
+ try {
1892
+ var t2 = tlsTransport(certPair, stJunk.port);
1893
+ var r2 = await t2.send({ from: "s@a.test", to: "r@b.test", subject: "s", text: "body bytes" });
1894
+ check("smtp-size-junk: delivered (junk SIZE arg ignored)", r2.code === 250);
1895
+ check("smtp-size-junk: MAIL FROM omits SIZE=", !/SIZE=/.test(stJunk.mailFromLine || ""));
1896
+ } finally { await closeServer(stJunk); }
1897
+ }
1898
+
1899
+ // ---------------------------------------------------------------------------
1900
+ // SMTP wire — 8BITMIME triggered by a non-ASCII calendar body, and SMTPUTF8
1901
+ // triggered by a non-ASCII cc recipient (the cc/bcc list branch of the
1902
+ // _messageRequiresSmtpUtf8 detector).
1903
+ // ---------------------------------------------------------------------------
1904
+
1905
+ async function testSmtp8BitMimeCalendarBody(certPair) {
1906
+ var st = startTlsSmtp(certPair, { ext: ["8BITMIME"] });
1907
+ await listen(st);
1908
+ try {
1909
+ var t = tlsTransport(certPair, st.port);
1910
+ var r = await t.send({
1911
+ from: "s@a.test", to: "r@b.test", subject: "invite",
1912
+ calendar: { method: "REQUEST", icalText: "BEGIN:VCALENDAR\r\nSUMMARY:grüße café ☕\r\nEND:VCALENDAR" },
1913
+ });
1914
+ check("smtp-8bit-cal: delivered", r.code === 250);
1915
+ check("smtp-8bit-cal: non-ASCII calendar body → BODY=8BITMIME",
1916
+ /BODY=8BITMIME/.test(st.mailFromLine || ""));
1917
+ } finally { await closeServer(st); }
1918
+ }
1919
+
1920
+ async function testSmtpSmtpUtf8FromCcRecipient(certPair) {
1921
+ var st = startTlsSmtp(certPair, { ext: ["SMTPUTF8", "8BITMIME"] });
1922
+ await listen(st);
1923
+ try {
1924
+ var t = tlsTransport(certPair, st.port);
1925
+ // ASCII from / to / subject; only the cc carries non-ASCII → the cc/bcc
1926
+ // scan in _messageRequiresSmtpUtf8 must flip the transaction to SMTPUTF8.
1927
+ var r = await t.send({
1928
+ from: "s@a.test", to: "r@b.test", cc: "grüße@münchen.de", subject: "hi", text: "hello",
1929
+ });
1930
+ check("smtp-utf8-cc: delivered", r.code === 250);
1931
+ check("smtp-utf8-cc: non-ASCII cc recipient → MAIL FROM carried SMTPUTF8",
1932
+ /SMTPUTF8/.test(st.mailFromLine || ""));
1933
+ check("smtp-utf8-cc: cc added to RCPT set", st.rcptLines.length === 2);
1934
+ } finally { await closeServer(st); }
1935
+ }
1936
+
1937
+ // ---------------------------------------------------------------------------
1938
+
418
1939
  async function run() {
419
1940
  testSmtpTransportAcceptsChunkingOpts();
420
1941
  testSmtpTransportRefusesBadHost();
@@ -431,6 +1952,79 @@ async function run() {
431
1952
  await testGuardDomainPermissiveProfileAllowsBareIp();
432
1953
  await testGuardDomainHappyPath();
433
1954
  await testGuardDomainValidatesFromAddress();
1955
+
1956
+ // Pure / no-socket branches.
1957
+ testToAsciiBranches();
1958
+ testToUnicodeBranches();
1959
+ testBuildRfc822MultipartAlternative();
1960
+ testBuildRfc822CalendarCcReplyToHeaders();
1961
+ testBuildRfc822AttachmentsAndDotStuffing();
1962
+ await testConsoleTransport();
1963
+ await testMemoryTransport();
1964
+ testHttpTransportConfigGates();
1965
+ await testHttpTransportBadSerializer();
1966
+ await testHttpTransportRequestFailureWrap();
1967
+ await testHttpTransportSuccessAndInterpret();
1968
+ testResendConfigGate();
1969
+ await testResendOverLoopback();
1970
+ await testResendBadResponseAndNoId();
1971
+ testCreateOptsAndTransportShapes();
1972
+ await testCreateTransportErrorWrap();
1973
+ await testCreateUnsubscribeExpansion();
1974
+ testCreateFooterHtmlValidation();
1975
+ await testCommercialHtmlOnlyAndStringAddress();
1976
+ await testValidateMessageBranches();
1977
+ await testValidateCalendarBranches();
1978
+ await testValidateAttachmentBranches();
1979
+ await testEaiRecipientAccepted();
1980
+ testSmtpConfigGates();
1981
+ await testSmtpDkimSignFailureRejectsPreConnect();
1982
+ await testReverseDnsErrorShape();
1983
+
1984
+ // Error / adversarial / defensive branches added for coverage.
1985
+ testFeedbackId();
1986
+ testToAsciiPunycodeEmptyReturnsNull();
1987
+ testCreateCommercialPostalAddressValidation();
1988
+ await testCommercialSendCanSpamAndObjectFooter();
1989
+ await testMessageHeaderInjectionBranches();
1990
+ await testMergeMessageHeaderMerge();
1991
+ testSmtpRejectUnauthorizedAudits();
1992
+
1993
+ // Additional error / edge / permutation branches.
1994
+ await testEaiAndLengthValidationEdges();
1995
+ await testCreateGuardDomainProfileFallback();
1996
+ await testCommercialCustomFooterSeparator();
1997
+ await testConsoleSingleCc();
1998
+ await testHttpMethodAndVerdictNoId();
1999
+ await testResendMinimalAndNoMessageReason();
2000
+ testSmtpOptionPermutations();
2001
+
2002
+ // SMTP state machine over loopback TLS — mint one cert pair, reuse.
2003
+ var ca = await b.mtlsEngine.generateCa({ generation: 1 });
2004
+ var leaf = await b.mtlsEngine.signClientCert({
2005
+ cn: "localhost", caCertPem: ca.caCertPem, caKeyPem: ca.caKeyPem,
2006
+ usage: "server", sans: ["IP:127.0.0.1", "localhost"],
2007
+ });
2008
+ var certPair = { key: leaf.key, cert: leaf.cert, caCertPem: ca.caCertPem };
2009
+
2010
+ await testSmtpHappyPathData(certPair);
2011
+ await testSmtpAuthLogin(certPair);
2012
+ await testSmtpBdatChunking(certPair);
2013
+ await testSmtpBinaryMimeBdat(certPair);
2014
+ await testSmtpSmtpUtf8And8BitMime(certPair);
2015
+ await testSmtpSizeRefusal(certPair);
2016
+ await testSmtpEaiUnsupportedRefusal(certPair);
2017
+ await testSmtpBinaryMimeUnsupportedRefusal(certPair);
2018
+ await testSmtpRejectionCodes(certPair);
2019
+ await testSmtpResponseTooLarge(certPair);
2020
+ await testSmtpTransactionTimeout(certPair);
2021
+ await testSmtpSocketTimeout(certPair);
2022
+ await testSmtpStarttlsHappyPath(certPair);
2023
+ await testSmtpStarttlsRejected(certPair);
2024
+ await testSmtpBinaryMimeFromNulBuffer(certPair);
2025
+ await testSmtpParsePeerSizeNoCapAndJunk(certPair);
2026
+ await testSmtp8BitMimeCalendarBody(certPair);
2027
+ await testSmtpSmtpUtf8FromCcRecipient(certPair);
434
2028
  }
435
2029
 
436
2030
  module.exports = { run: run };