@blamejs/blamejs-shop 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/SECURITY.md +17 -0
  4. package/lib/addresses.js +2 -2
  5. package/lib/admin.js +49 -45
  6. package/lib/affiliates.js +7 -23
  7. package/lib/announcement-bar.js +5 -22
  8. package/lib/api-keys.js +4 -13
  9. package/lib/asset-manifest.json +1 -1
  10. package/lib/banner-ab-tests.js +5 -9
  11. package/lib/blog-articles.js +8 -26
  12. package/lib/captcha-gate.js +7 -17
  13. package/lib/carrier-accounts.js +3 -10
  14. package/lib/catalog-drafts.js +5 -12
  15. package/lib/category-navigation.js +4 -20
  16. package/lib/cms-blocks.js +4 -7
  17. package/lib/compliance-export.js +4 -11
  18. package/lib/consent-ledger.js +2 -4
  19. package/lib/cost-layers.js +56 -21
  20. package/lib/customer-import.js +2 -2
  21. package/lib/customer-notes.js +7 -23
  22. package/lib/customer-roles.js +3 -5
  23. package/lib/customer-segments.js +17 -14
  24. package/lib/customer-surveys.js +7 -14
  25. package/lib/customers.js +2 -4
  26. package/lib/dispute-resolution.js +5 -13
  27. package/lib/dropship-forwarding.js +3 -5
  28. package/lib/dunning.js +2 -7
  29. package/lib/email-ab-tests.js +4 -8
  30. package/lib/email-campaigns.js +25 -14
  31. package/lib/email-warmup.js +2 -7
  32. package/lib/experiments.js +6 -10
  33. package/lib/externaldb-d1.js +78 -44
  34. package/lib/geolocation.js +18 -28
  35. package/lib/gift-options.js +5 -23
  36. package/lib/knowledge-base.js +7 -22
  37. package/lib/line-gift-wrap.js +3 -17
  38. package/lib/live-chat.js +5 -18
  39. package/lib/metered-usage.js +3 -8
  40. package/lib/notifications.js +3 -7
  41. package/lib/operator-accounts.js +3 -5
  42. package/lib/operator-approvals.js +6 -8
  43. package/lib/operator-audit-log.js +2 -7
  44. package/lib/operator-help-center.js +5 -21
  45. package/lib/operator-inbox.js +4 -11
  46. package/lib/operator-roles.js +5 -7
  47. package/lib/order-escalation.js +2 -2
  48. package/lib/order-export.js +24 -20
  49. package/lib/order-notes.js +4 -14
  50. package/lib/order-ratings.js +4 -10
  51. package/lib/payment-methods.js +5 -13
  52. package/lib/payment-retries.js +2 -7
  53. package/lib/payment.js +103 -58
  54. package/lib/pixel-events.js +8 -23
  55. package/lib/plan-changes.js +2 -7
  56. package/lib/product-qa.js +4 -8
  57. package/lib/promo-banners.js +7 -15
  58. package/lib/purchase-orders.js +3 -6
  59. package/lib/push-notifications.js +7 -32
  60. package/lib/pwa-manifest.js +6 -11
  61. package/lib/quotes.js +5 -9
  62. package/lib/referrals.js +10 -6
  63. package/lib/reviews.js +4 -10
  64. package/lib/robots-config.js +6 -14
  65. package/lib/sales-tax-filings.js +2 -2
  66. package/lib/search-facets.js +5 -5
  67. package/lib/search-ranking.js +4 -7
  68. package/lib/search-synonyms.js +7 -21
  69. package/lib/security-middleware.js +125 -70
  70. package/lib/seller-signup.js +10 -26
  71. package/lib/sidebar-widgets.js +6 -15
  72. package/lib/site-redirects.js +49 -137
  73. package/lib/sitemap-generator.js +3 -7
  74. package/lib/sms-dispatcher.js +7 -23
  75. package/lib/stock-receipts.js +4 -10
  76. package/lib/storefront-dashboards.js +2 -10
  77. package/lib/storefront-forms.js +9 -22
  78. package/lib/storefront-pages.js +6 -20
  79. package/lib/storefront.js +21 -23
  80. package/lib/subscription-billing.js +3 -8
  81. package/lib/subscription-controls.js +2 -7
  82. package/lib/suggestion-box.js +9 -26
  83. package/lib/support-tickets.js +6 -24
  84. package/lib/tax-remittance.js +2 -2
  85. package/lib/tenants.js +2 -7
  86. package/lib/text-guard.js +137 -35
  87. package/lib/theme-assets.js +4 -16
  88. package/lib/translations.js +3 -6
  89. package/lib/trust-badges.js +4 -7
  90. package/lib/vendor/MANIFEST.json +107 -97
  91. package/lib/vendor/blamejs/CHANGELOG.md +180 -0
  92. package/lib/vendor/blamejs/NOTICE +14 -10
  93. package/lib/vendor/blamejs/README.md +17 -5
  94. package/lib/vendor/blamejs/index.js +2 -0
  95. package/lib/vendor/blamejs/lib/acme.js +9 -9
  96. package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
  97. package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
  98. package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
  99. package/lib/vendor/blamejs/lib/archive.js +13 -24
  100. package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
  101. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
  102. package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
  103. package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
  104. package/lib/vendor/blamejs/lib/auth/password.js +1 -1
  105. package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
  106. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
  107. package/lib/vendor/blamejs/lib/backup/index.js +5 -5
  108. package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
  109. package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
  110. package/lib/vendor/blamejs/lib/constants.js +79 -0
  111. package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
  112. package/lib/vendor/blamejs/lib/error-page.js +20 -1
  113. package/lib/vendor/blamejs/lib/external-db.js +5 -1
  114. package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
  115. package/lib/vendor/blamejs/lib/flag.js +28 -2
  116. package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
  117. package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
  118. package/lib/vendor/blamejs/lib/http-client.js +217 -40
  119. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  120. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
  121. package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
  122. package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
  123. package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
  124. package/lib/vendor/blamejs/lib/mail-store.js +7 -1
  125. package/lib/vendor/blamejs/lib/mail.js +5 -1
  126. package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
  127. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
  128. package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
  129. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
  130. package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
  131. package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
  132. package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
  133. package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
  134. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
  135. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
  136. package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
  137. package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
  138. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
  139. package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
  140. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
  141. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
  142. package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
  143. package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
  144. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
  145. package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
  146. package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
  147. package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
  148. package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
  149. package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
  150. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  151. package/lib/vendor/blamejs/lib/network-dns.js +3 -3
  152. package/lib/vendor/blamejs/lib/network-nts.js +35 -14
  153. package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
  154. package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
  155. package/lib/vendor/blamejs/lib/network-tls.js +341 -11
  156. package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
  157. package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
  158. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
  160. package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
  161. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
  162. package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
  163. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
  164. package/lib/vendor/blamejs/lib/openapi.js +1 -1
  165. package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
  166. package/lib/vendor/blamejs/lib/problem-details.js +2 -1
  167. package/lib/vendor/blamejs/lib/redis-client.js +12 -1
  168. package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
  169. package/lib/vendor/blamejs/lib/render.js +531 -7
  170. package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
  171. package/lib/vendor/blamejs/lib/router.js +37 -13
  172. package/lib/vendor/blamejs/lib/safe-async.js +142 -0
  173. package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
  174. package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
  175. package/lib/vendor/blamejs/lib/self-update.js +2 -2
  176. package/lib/vendor/blamejs/lib/testing.js +54 -7
  177. package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
  178. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
  179. package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
  180. package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
  181. package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
  182. package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
  183. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
  184. package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
  185. package/lib/vendor/blamejs/lib/webhook.js +1 -1
  186. package/lib/vendor/blamejs/lib/ws-client.js +24 -2
  187. package/lib/vendor/blamejs/package.json +1 -1
  188. package/lib/vendor/blamejs/sbom.cdx.json +6 -6
  189. package/lib/vendor-invoices.js +3 -6
  190. package/lib/vendors.js +5 -19
  191. package/lib/webhook-receiver.js +46 -83
  192. package/lib/webhook-subscriptions.js +3 -13
  193. package/lib/wishlist-sharing.js +5 -10
  194. package/package.json +1 -1
package/lib/text-guard.js CHANGED
@@ -36,7 +36,11 @@
36
36
  * - slugLabel(s, label, opts?) — lowercase URL-slug label
37
37
  * - hostLabel(host, label?) — outbound host SSRF + by-name denylist
38
38
  * - freeText(s, label, policy?) — dangerous-codepoint screen for
39
- * unconstrained UTF-8 text fields
39
+ * unconstrained UTF-8 text fields, throwing on a refusal
40
+ * - hasCodepointThreat(s, policy?) — the same screen for a defensive
41
+ * reader that DROPS a bad value instead of refusing the request
42
+ * - firstCodepointThreat(s, policy?) — the one decision both run, so
43
+ * the throwing and dropping paths cannot disagree
40
44
  *
41
45
  * @primitive textGuard
42
46
  * @related b.money, b.ssrfGuard, b.safeUrl, shop.currencyRounding,
@@ -52,6 +56,41 @@ var b = require("./vendor/blamejs");
52
56
  // second copy of the tables.
53
57
  var codepointClass = b.codepointClass;
54
58
 
59
+ // Two invisible-codepoint groups this shop refuses that the framework catalog
60
+ // does not carry yet. Built with codepointClass.charClass off a range table so
61
+ // the escaping is the catalog's, not a second hand-spelled class:
62
+ //
63
+ // U+007F DELETE — grouped with the C0 controls everywhere it matters, but
64
+ // outside the 0x00-0x1F block the framework's C0_CTRL_RANGES covers.
65
+ // U+2061-2064 FUNCTION APPLICATION / INVISIBLE TIMES / INVISIBLE SEPARATOR /
66
+ // INVISIBLE PLUS — zero-width invisible operators, the same spoofing class
67
+ // as U+2060 WORD JOINER, which the framework's ZERO_WIDTH_RANGES does have.
68
+ //
69
+ // Requested upstream (blamejs#580). When a vendor refresh lands them in the
70
+ // catalog this supplement becomes dead weight and comes out — the drift test
71
+ // in test/layer-1-state/text-guard.test.js fails the moment that happens, so
72
+ // it cannot be silently carried forever.
73
+ //
74
+ // Kept as two classes rather than one so each joins the check whose refusal
75
+ // message is already true of it.
76
+ // allow:dynamic-regex — codepoints from a literal range table
77
+ var DEL_RE = new RegExp("[" + codepointClass.charClass([0x007F]) + "]");
78
+ // allow:dynamic-regex — codepoints from a literal range table
79
+ var INVISIBLE_OP_RE = new RegExp("[" + codepointClass.charClass([[0x2061, 0x2064]]) + "]");
80
+ // Global twin for the strip pass; the unglobbed one above is the presence test.
81
+ // allow:dynamic-regex — codepoints from a literal range table
82
+ var INVISIBLE_OP_RE_G = new RegExp("[" + codepointClass.charClass([[0x2061, 0x2064]]) + "]", "g");
83
+
84
+ // Everything that ends a line or pads it out, for a value that must stay on
85
+ // one. Tab / LF / CR sit outside the framework's C0 table on purpose — a
86
+ // multi-line body needs them — so a single-line field asks for them here.
87
+ // U+2028 / U+2029 are not control characters at all, but they terminate a
88
+ // line for both a renderer and a JS string literal.
89
+ // allow:dynamic-regex — codepoints from a literal range table
90
+ var LINE_BREAK_RE = new RegExp(
91
+ "[" + codepointClass.charClass([0x0009, 0x000A, 0x000D, [0x2028, 0x2029]]) + "]"
92
+ );
93
+
55
94
  // ---- shop validators ----------------------------------------------------
56
95
 
57
96
  // asciiUpperLetters(s, label, n?) — exactly `n` (default 3) ASCII
@@ -157,45 +196,105 @@ function hostLabel(host, label) {
157
196
  return h;
158
197
  }
159
198
 
160
- // freeText(s, label, policy?) — the dangerous-codepoint screen for an
161
- // UNCONSTRAINED UTF-8 text field (customer note, review body, gift
162
- // message, product Q&A). Unlike the ASCII-allowlist validators above,
163
- // these fields legitimately carry arbitrary letters, so the codepoint
164
- // catalog is the right backing: bidi overrides (CVE-2021-42574 Trojan
165
- // Source), null bytes, and C0 control characters are refused by default;
166
- // zero-width / invisible formatting is refused when policy.zeroWidth is
167
- // "reject"; a mixed-script confusable (Cyrillic 'а' inside an otherwise
168
- // Latin label) is refused when policy.mixedScript is "reject". Throws
169
- // TypeError on a refused codepoint. Returns the input on success.
170
- function freeText(s, label, policy) {
171
- var name = label || "value";
172
- if (typeof s !== "string") {
173
- throw new TypeError(name + " must be a string");
174
- }
199
+ // firstCodepointThreat(s, policy?) — the single decision both the throwing
200
+ // screen (freeText) and the drop-silent one (hasCodepointThreat) run, so a
201
+ // field guarded one way and a field guarded the other can never disagree
202
+ // about what "dangerous" means. Returns a short reason string naming the
203
+ // class, or null when the text is clean. The reason is the clause that
204
+ // follows the field name in a refusal ("contains a null byte"), so a caller
205
+ // building a message just joins the two.
206
+ //
207
+ // Policies, each "reject" to enable and any other value to allow:
208
+ // bidi — default "reject". Bidi overrides (CVE-2021-42574).
209
+ // nullByte — default "reject". U+0000.
210
+ // control — default "reject". C0 controls + U+007F DELETE. Tab, LF and
211
+ // CR are ALLOWED here: a multi-line body legitimately has
212
+ // them. Use singleLine for a field that must not.
213
+ // singleLine — off by default. Tab / LF / CR, plus U+2028 LINE SEPARATOR
214
+ // and U+2029 PARAGRAPH SEPARATOR, which break a line without
215
+ // being C0 at all. For a value rendered on one line — a URL,
216
+ // a subject, a title, a header value.
217
+ // zeroWidth — off by default. Zero-width / invisible formatting. Includes
218
+ // U+00AD SOFT HYPHEN, which text pasted out of a hyphenating
219
+ // word processor or PDF carries invisibly.
220
+ // mixedScript — off by default. Confusable / homograph across writing
221
+ // systems, narrowed by policy.allowedScripts.
222
+ function firstCodepointThreat(s, policy) {
175
223
  policy = policy || {};
176
- var bidiP = policy.bidi || "reject";
177
- var nullP = policy.nullByte || "reject";
178
- var controlP = policy.control || "reject";
179
- if (bidiP === "reject" && codepointClass.BIDI_RE.test(s)) {
180
- throw new TypeError(name + " contains a Unicode bidi override (CVE-2021-42574 Trojan Source)");
224
+ if ((policy.bidi || "reject") === "reject" && codepointClass.BIDI_RE.test(s)) {
225
+ return "contains a Unicode bidi override (CVE-2021-42574 Trojan Source)";
181
226
  }
182
- if (nullP === "reject" && s.indexOf(codepointClass.NULL_BYTE) !== -1) {
183
- throw new TypeError(name + " contains a null byte");
227
+ if ((policy.nullByte || "reject") === "reject" &&
228
+ s.indexOf(codepointClass.NULL_BYTE) !== -1) {
229
+ return "contains a null byte";
184
230
  }
185
- if (controlP === "reject" && codepointClass.C0_CTRL_RE.test(s)) {
186
- throw new TypeError(name + " contains a C0 control character");
231
+ if ((policy.control || "reject") === "reject" &&
232
+ (codepointClass.C0_CTRL_RE.test(s) || DEL_RE.test(s))) {
233
+ return "contains a C0 control character";
187
234
  }
188
- if (policy.zeroWidth === "reject" && codepointClass.ZERO_WIDTH_RE.test(s)) {
189
- throw new TypeError(name + " contains a zero-width / invisible formatting character");
235
+ if (policy.singleLine === "reject" && LINE_BREAK_RE.test(s)) {
236
+ return "contains a line break or tab but must be a single line";
237
+ }
238
+ if (policy.zeroWidth === "reject" &&
239
+ (codepointClass.ZERO_WIDTH_RE.test(s) || INVISIBLE_OP_RE.test(s))) {
240
+ return "contains a zero-width / invisible formatting character";
190
241
  }
191
242
  if (policy.mixedScript === "reject") {
192
243
  var scripts = codepointClass.detectMixedScripts(s, policy.allowedScripts);
193
244
  if (scripts) {
194
- throw new TypeError(
195
- name + " mixes writing systems (" + scripts.join(", ") + ") — possible confusable / homograph"
196
- );
245
+ return "mixes writing systems (" + scripts.join(", ") +
246
+ ") — possible confusable / homograph";
197
247
  }
198
248
  }
249
+ return null;
250
+ }
251
+
252
+ // hasCodepointThreat(s, policy?) — the non-throwing sibling of freeText, for
253
+ // the defensive readers that DROP a bad value rather than refusing the
254
+ // request (a link URL inside a rendered body, a header line being assembled).
255
+ // A non-string is dangerous by definition here: the caller is about to render
256
+ // or transmit it. Returns true when the text must not be used.
257
+ function hasCodepointThreat(s, policy) {
258
+ if (typeof s !== "string") return true;
259
+ return firstCodepointThreat(s, policy) !== null;
260
+ }
261
+
262
+ // scrubInvisible(s) — remove every bidi override and invisible-formatting
263
+ // codepoint and return the cleaned string. The third disposition, alongside
264
+ // throwing and dropping: for a value that must be ACCEPTED whatever it
265
+ // contains, because refusing it would hand an attacker a denial-of-service
266
+ // against a legitimate user (a search query is the standing example — a
267
+ // pasted BOM must not cost the shopper their search).
268
+ //
269
+ // Composes the framework's strip pass and then removes the codepoints the
270
+ // catalog does not carry yet, so what this deletes is exactly what
271
+ // hasCodepointThreat would have objected to under zeroWidth: "reject".
272
+ // Control characters are deliberately NOT touched: a caller stripping those
273
+ // usually wants to substitute a space to avoid welding two words together,
274
+ // which is its own decision.
275
+ function scrubInvisible(s) {
276
+ if (typeof s !== "string") return s;
277
+ var out = codepointClass.applyCharStripPolicies(s, {
278
+ bidiPolicy: "strip",
279
+ zeroWidthPolicy: "strip",
280
+ tagsPolicy: "strip",
281
+ });
282
+ return out.replace(INVISIBLE_OP_RE_G, "");
283
+ }
284
+
285
+ // freeText(s, label, policy?) — the dangerous-codepoint screen for an
286
+ // UNCONSTRAINED UTF-8 text field (customer note, review body, gift
287
+ // message, product Q&A). Unlike the ASCII-allowlist validators above,
288
+ // these fields legitimately carry arbitrary letters, so the codepoint
289
+ // catalog is the right backing. Throws TypeError on a refused codepoint;
290
+ // returns the input on success. See firstCodepointThreat for the policies.
291
+ function freeText(s, label, policy) {
292
+ var name = label || "value";
293
+ if (typeof s !== "string") {
294
+ throw new TypeError(name + " must be a string");
295
+ }
296
+ var reason = firstCodepointThreat(s, policy);
297
+ if (reason) throw new TypeError(name + " " + reason);
199
298
  return s;
200
299
  }
201
300
 
@@ -213,9 +312,12 @@ module.exports = {
213
312
  applyCharStripPolicies: codepointClass.applyCharStripPolicies,
214
313
 
215
314
  // Shop validators.
216
- asciiUpperLetters: asciiUpperLetters,
217
- currencyCode: currencyCode,
218
- slugLabel: slugLabel,
219
- hostLabel: hostLabel,
220
- freeText: freeText,
315
+ asciiUpperLetters: asciiUpperLetters,
316
+ currencyCode: currencyCode,
317
+ slugLabel: slugLabel,
318
+ hostLabel: hostLabel,
319
+ freeText: freeText,
320
+ hasCodepointThreat: hasCodepointThreat,
321
+ firstCodepointThreat: firstCodepointThreat,
322
+ scrubInvisible: scrubInvisible,
221
323
  };
@@ -84,6 +84,7 @@
84
84
  */
85
85
 
86
86
  var b = require("./vendor/blamejs");
87
+ var textGuard = require("./text-guard");
87
88
 
88
89
  var MAX_SLUG_LEN = 80;
89
90
  var MAX_THEME_SLUG_LEN = 80;
@@ -130,14 +131,10 @@ var SHA3_512_HEX_RE = /^[0-9a-f]{128}$/;
130
131
  var CONTENT_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:\s*;\s*[A-Za-z0-9!#$&^_.+-]+\s*=\s*(?:"[^"\x00-\x1f]*"|[A-Za-z0-9!#$&^_.+-]+))*$/;
131
132
 
132
133
  // Refuse C0 control bytes + DEL in operator-authored text fields.
133
- var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
134
134
 
135
135
  // Zero-width / direction-override family — mirrors the rest of the
136
136
  // shop primitives. Spelled with \u-escapes so ESLint's
137
137
  // no-irregular-whitespace stays happy.
138
- var ZERO_WIDTH_RE = new RegExp(
139
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
140
- );
141
138
 
142
139
  var MS_PER_DAY = b.constants.TIME.days(1);
143
140
 
@@ -181,9 +178,7 @@ function _contentType(s) {
181
178
  MAX_CONTENT_TYPE_LEN + " chars"
182
179
  );
183
180
  }
184
- if (CONTROL_BYTE_LINE_RE.test(s)) {
185
- throw new TypeError("themeAssets: content_type contains control bytes");
186
- }
181
+ textGuard.freeText(s, "themeAssets: content_type", { singleLine: "reject" });
187
182
  if (!CONTENT_TYPE_RE.test(s)) {
188
183
  throw new TypeError(
189
184
  "themeAssets: content_type " + JSON.stringify(s) +
@@ -230,9 +225,7 @@ function _sourceUrl(s) {
230
225
  MAX_SOURCE_URL_LEN + " chars"
231
226
  );
232
227
  }
233
- if (CONTROL_BYTE_LINE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
234
- throw new TypeError("themeAssets: source_url contains control / zero-width bytes");
235
- }
228
+ textGuard.freeText(s, "themeAssets: source_url", { singleLine: "reject", zeroWidth: "reject" });
236
229
  if (s.charCodeAt(0) === 47 /* "/" */) {
237
230
  if (s.length > 1 && s.charCodeAt(1) === 47) {
238
231
  throw new TypeError(
@@ -264,12 +257,7 @@ function _altText(s) {
264
257
  MAX_ALT_TEXT_LEN + " chars (or null)"
265
258
  );
266
259
  }
267
- if (CONTROL_BYTE_LINE_RE.test(s)) {
268
- throw new TypeError("themeAssets: alt_text contains control bytes (incl. CR/LF)");
269
- }
270
- if (ZERO_WIDTH_RE.test(s)) {
271
- throw new TypeError("themeAssets: alt_text contains zero-width / direction-override characters");
272
- }
260
+ textGuard.freeText(s, "themeAssets: alt_text", { singleLine: "reject", zeroWidth: "reject" });
273
261
  return s;
274
262
  }
275
263
 
@@ -58,6 +58,7 @@
58
58
  */
59
59
 
60
60
  var b = require("./vendor/blamejs");
61
+ var textGuard = require("./text-guard");
61
62
 
62
63
  var MAX_KIND_LEN = 64;
63
64
  var MAX_RESOURCE_ID_LEN = 128;
@@ -90,14 +91,10 @@ var LOCALE_RE = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
90
91
  // allowed (operators author multi-line page bodies) — the rest of
91
92
  // the C0 range would only reach the storefront as a smuggling
92
93
  // payload or a copy-paste accident from a terminal.
93
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
94
94
 
95
95
  // Zero-width / direction-override family. Spelled with \u-escapes
96
96
  // so ESLint's no-irregular-whitespace stays happy. Same defence the
97
97
  // rest of the shop uses on operator-authored strings.
98
- var ZERO_WIDTH_RE = new RegExp(
99
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
100
- );
101
98
 
102
99
  // ---- validators ---------------------------------------------------------
103
100
 
@@ -193,10 +190,10 @@ function _value(s, fnLabel) {
193
190
  if (s.length > MAX_VALUE_LEN) {
194
191
  throw new TypeError(fnLabel + ": value must be <= " + MAX_VALUE_LEN + " characters");
195
192
  }
196
- if (CONTROL_BYTE_RE.test(s)) {
193
+ if (textGuard.hasCodepointThreat(s)) {
197
194
  throw new TypeError(fnLabel + ": value contains forbidden control bytes");
198
195
  }
199
- if (ZERO_WIDTH_RE.test(s)) {
196
+ if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
200
197
  throw new TypeError(fnLabel + ": value contains zero-width / direction-override characters");
201
198
  }
202
199
  return s;
@@ -116,13 +116,9 @@ var EVENT_TYPES = Object.freeze(["impression", "click"]);
116
116
  var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
117
117
 
118
118
  // Refuse C0 control bytes + DEL in single-line operator strings.
119
- var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
120
119
 
121
120
  // Zero-width / direction-override family — same catalogue as the
122
121
  // other operator-authored primitives.
123
- var ZERO_WIDTH_RE = new RegExp(
124
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
125
- );
126
122
 
127
123
  // Hash namespace — opaque per-primitive constant so a session_id_hash
128
124
  // emitted by trustBadges can't collide with one emitted by analytics
@@ -142,6 +138,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
142
138
  ]);
143
139
 
144
140
  var b = require("./vendor/blamejs");
141
+ var textGuard = require("./text-guard");
145
142
 
146
143
  // ---- validators ---------------------------------------------------------
147
144
 
@@ -184,10 +181,10 @@ function _line(s, label, maxLen) {
184
181
  if (typeof s !== "string" || !s.length || s.length > maxLen) {
185
182
  throw new TypeError("trustBadges: " + label + " must be a non-empty string ≤ " + maxLen + " chars");
186
183
  }
187
- if (CONTROL_BYTE_LINE_RE.test(s)) {
184
+ if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
188
185
  throw new TypeError("trustBadges: " + label + " contains control bytes (incl. CR/LF)");
189
186
  }
190
- if (ZERO_WIDTH_RE.test(s)) {
187
+ if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
191
188
  throw new TypeError("trustBadges: " + label + " contains zero-width / direction-override characters");
192
189
  }
193
190
  return s;
@@ -201,7 +198,7 @@ function _httpsOrRootUrl(s, label, maxLen) {
201
198
  if (typeof s !== "string" || !s.length || s.length > maxLen) {
202
199
  throw new TypeError("trustBadges: " + label + " must be a non-empty string ≤ " + maxLen + " chars");
203
200
  }
204
- if (CONTROL_BYTE_LINE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
201
+ if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
205
202
  throw new TypeError("trustBadges: " + label + " contains control / zero-width bytes");
206
203
  }
207
204
  if (s.charCodeAt(0) === 47 /* "/" */) {