@blamejs/blamejs-shop 0.5.19 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +3 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/referrals.js +10 -6
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +21 -23
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +137 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
package/lib/blog-articles.js
CHANGED
|
@@ -158,16 +158,12 @@ var PRODUCT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
|
158
158
|
// body permits LF (Markdown is line-oriented); single-line fields
|
|
159
159
|
// refuse LF / CR so they can't smuggle a second line into a post
|
|
160
160
|
// header / meta tag / hero-image attribute.
|
|
161
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
162
|
-
var CONTROL_BYTE_BLOCK_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
163
161
|
|
|
164
162
|
// Zero-width / direction-override family — spelled with \u-escapes so
|
|
165
163
|
// ESLint's no-irregular-whitespace stays happy.
|
|
166
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
167
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
168
|
-
);
|
|
169
164
|
|
|
170
165
|
var b = require("./vendor/blamejs");
|
|
166
|
+
var textGuard = require("./text-guard");
|
|
171
167
|
|
|
172
168
|
// ---- monotonic clock ---------------------------------------------------
|
|
173
169
|
//
|
|
@@ -204,12 +200,7 @@ function _title(s) {
|
|
|
204
200
|
if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
|
|
205
201
|
throw new TypeError("blogArticles: title must be a non-empty string <= " + MAX_TITLE_LEN + " chars");
|
|
206
202
|
}
|
|
207
|
-
|
|
208
|
-
throw new TypeError("blogArticles: title contains control bytes (incl. CR/LF)");
|
|
209
|
-
}
|
|
210
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
211
|
-
throw new TypeError("blogArticles: title contains zero-width / direction-override characters");
|
|
212
|
-
}
|
|
203
|
+
textGuard.freeText(s, "blogArticles: title", { singleLine: "reject", zeroWidth: "reject" });
|
|
213
204
|
return s;
|
|
214
205
|
}
|
|
215
206
|
|
|
@@ -217,12 +208,7 @@ function _body(s) {
|
|
|
217
208
|
if (typeof s !== "string" || !s.length || s.length > MAX_BODY_LEN) {
|
|
218
209
|
throw new TypeError("blogArticles: body must be a non-empty string <= " + MAX_BODY_LEN + " chars");
|
|
219
210
|
}
|
|
220
|
-
|
|
221
|
-
throw new TypeError("blogArticles: body contains control bytes");
|
|
222
|
-
}
|
|
223
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
224
|
-
throw new TypeError("blogArticles: body contains zero-width / direction-override characters");
|
|
225
|
-
}
|
|
211
|
+
textGuard.freeText(s, "blogArticles: body", { zeroWidth: "reject" });
|
|
226
212
|
return s;
|
|
227
213
|
}
|
|
228
214
|
|
|
@@ -306,9 +292,7 @@ function _heroImageUrl(s) {
|
|
|
306
292
|
if (s.length > MAX_HERO_IMAGE_URL_LEN) {
|
|
307
293
|
throw new TypeError("blogArticles: hero_image_url must be <= " + MAX_HERO_IMAGE_URL_LEN + " chars");
|
|
308
294
|
}
|
|
309
|
-
|
|
310
|
-
throw new TypeError("blogArticles: hero_image_url contains control / zero-width bytes");
|
|
311
|
-
}
|
|
295
|
+
textGuard.freeText(s, "blogArticles: hero_image_url", { singleLine: "reject", zeroWidth: "reject" });
|
|
312
296
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
313
297
|
if (s.length > 1 && s.charCodeAt(1) === 47) {
|
|
314
298
|
throw new TypeError("blogArticles: hero_image_url protocol-relative `//host/...` refused — use absolute https://");
|
|
@@ -331,10 +315,10 @@ function _metaLine(s, label, maxLen) {
|
|
|
331
315
|
if (typeof s !== "string" || s.length > maxLen) {
|
|
332
316
|
throw new TypeError("blogArticles: " + label + " must be a string <= " + maxLen + " chars");
|
|
333
317
|
}
|
|
334
|
-
if (
|
|
318
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
335
319
|
throw new TypeError("blogArticles: " + label + " contains control bytes (incl. CR/LF)");
|
|
336
320
|
}
|
|
337
|
-
if (
|
|
321
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
338
322
|
throw new TypeError("blogArticles: " + label + " contains zero-width / direction-override characters");
|
|
339
323
|
}
|
|
340
324
|
return s;
|
|
@@ -367,9 +351,7 @@ function _sessionIdRaw(s) {
|
|
|
367
351
|
if (s.length > 256) {
|
|
368
352
|
throw new TypeError("blogArticles: session_id must be <= 256 characters");
|
|
369
353
|
}
|
|
370
|
-
|
|
371
|
-
throw new TypeError("blogArticles: session_id contains control / zero-width bytes");
|
|
372
|
-
}
|
|
354
|
+
textGuard.freeText(s, "blogArticles: session_id", { singleLine: "reject", zeroWidth: "reject" });
|
|
373
355
|
return s;
|
|
374
356
|
}
|
|
375
357
|
|
|
@@ -424,7 +406,7 @@ function _esc(s) {
|
|
|
424
406
|
|
|
425
407
|
function _safeLinkUrl(url) {
|
|
426
408
|
if (typeof url !== "string" || !url.length || url.length > 2048) return null;
|
|
427
|
-
if (
|
|
409
|
+
if (textGuard.hasCodepointThreat(url, { singleLine: "reject", zeroWidth: "reject" })) return null;
|
|
428
410
|
if (url.charCodeAt(0) === 47 /* "/" */) {
|
|
429
411
|
if (url.length > 1 && url.charCodeAt(1) === 47) return null;
|
|
430
412
|
if (url.indexOf("..") !== -1) return null;
|
package/lib/captcha-gate.js
CHANGED
|
@@ -165,7 +165,6 @@ var DEFAULT_LIST_LIMIT = 100;
|
|
|
165
165
|
var MAX_LIST_LIMIT = 500;
|
|
166
166
|
|
|
167
167
|
var SLUG_RE = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
168
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
169
168
|
var ACTION_RE = /^[A-Za-z0-9_./-]+$/;
|
|
170
169
|
|
|
171
170
|
var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
@@ -177,6 +176,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
|
177
176
|
|
|
178
177
|
// Framework handle (the vendored blamejs); index.js re-exports this as .framework.
|
|
179
178
|
var b = require("./vendor/blamejs");
|
|
179
|
+
var textGuard = require("./text-guard");
|
|
180
180
|
|
|
181
181
|
// ---- validators --------------------------------------------------------
|
|
182
182
|
|
|
@@ -224,9 +224,7 @@ function _publicKey(s) {
|
|
|
224
224
|
"captchaGate: public_key must be <= " + PUBLIC_KEY_MAX_LEN + " characters"
|
|
225
225
|
);
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
throw new TypeError("captchaGate: public_key contains control bytes");
|
|
229
|
-
}
|
|
227
|
+
textGuard.freeText(s, "captchaGate: public_key");
|
|
230
228
|
return s;
|
|
231
229
|
}
|
|
232
230
|
|
|
@@ -245,9 +243,7 @@ function _secretKeyRaw(s) {
|
|
|
245
243
|
"captchaGate: secret_key must be <= " + SECRET_KEY_MAX_LEN + " characters"
|
|
246
244
|
);
|
|
247
245
|
}
|
|
248
|
-
|
|
249
|
-
throw new TypeError("captchaGate: secret_key contains control bytes");
|
|
250
|
-
}
|
|
246
|
+
textGuard.freeText(trimmed, "captchaGate: secret_key");
|
|
251
247
|
return trimmed;
|
|
252
248
|
}
|
|
253
249
|
|
|
@@ -290,9 +286,7 @@ function _token(s) {
|
|
|
290
286
|
"captchaGate: token must be <= " + TOKEN_MAX_LEN + " characters"
|
|
291
287
|
);
|
|
292
288
|
}
|
|
293
|
-
|
|
294
|
-
throw new TypeError("captchaGate: token contains control bytes");
|
|
295
|
-
}
|
|
289
|
+
textGuard.freeText(s, "captchaGate: token");
|
|
296
290
|
return s;
|
|
297
291
|
}
|
|
298
292
|
|
|
@@ -337,10 +331,8 @@ function _optionalHash(s, label) {
|
|
|
337
331
|
"captchaGate: " + label + " must be <= " + HASH_MAX_LEN + " characters"
|
|
338
332
|
);
|
|
339
333
|
}
|
|
340
|
-
if (
|
|
341
|
-
throw new TypeError(
|
|
342
|
-
"captchaGate: " + label + " contains control bytes"
|
|
343
|
-
);
|
|
334
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
335
|
+
throw new TypeError("captchaGate: " + label + " contains control bytes");
|
|
344
336
|
}
|
|
345
337
|
return s;
|
|
346
338
|
}
|
|
@@ -355,9 +347,7 @@ function _optionalSessionId(s) {
|
|
|
355
347
|
"captchaGate: session_id must be <= " + SESSION_ID_MAX_LEN + " characters"
|
|
356
348
|
);
|
|
357
349
|
}
|
|
358
|
-
|
|
359
|
-
throw new TypeError("captchaGate: session_id contains control bytes");
|
|
360
|
-
}
|
|
350
|
+
textGuard.freeText(s, "captchaGate: session_id");
|
|
361
351
|
return s;
|
|
362
352
|
}
|
|
363
353
|
|
package/lib/carrier-accounts.js
CHANGED
|
@@ -101,6 +101,7 @@ var STATUSES = Object.freeze(["active", "disabled", "rotating"]);
|
|
|
101
101
|
|
|
102
102
|
// Framework handle (the vendored blamejs); index.js re-exports this as .framework.
|
|
103
103
|
var b = require("./vendor/blamejs");
|
|
104
|
+
var textGuard = require("./text-guard");
|
|
104
105
|
|
|
105
106
|
var ROTATION_GRACE_MS = b.constants.TIME.days(1);
|
|
106
107
|
|
|
@@ -124,10 +125,6 @@ var MAX_KEY_LEN = 512;
|
|
|
124
125
|
// reason / operation string. These columns surface on operator
|
|
125
126
|
// dashboards and inline into log lines — embedded control bytes are a
|
|
126
127
|
// slipping-class for header injection + visual spoofing.
|
|
127
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
128
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
129
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
130
|
-
);
|
|
131
128
|
|
|
132
129
|
// account_number alphabet — operators occasionally space-pad or
|
|
133
130
|
// hyphenate the printed form; we accept alnum + dash + space + dot and
|
|
@@ -233,9 +230,7 @@ function _label(s) {
|
|
|
233
230
|
if (s.length === 0 || s.length > MAX_LABEL_LEN) {
|
|
234
231
|
throw new TypeError("carrier-accounts: account_label must be 1.." + MAX_LABEL_LEN + " characters");
|
|
235
232
|
}
|
|
236
|
-
|
|
237
|
-
throw new TypeError("carrier-accounts: account_label contains control / zero-width bytes");
|
|
238
|
-
}
|
|
233
|
+
textGuard.freeText(s, "carrier-accounts: account_label", { singleLine: "reject", zeroWidth: "reject" });
|
|
239
234
|
return s;
|
|
240
235
|
}
|
|
241
236
|
|
|
@@ -250,9 +245,7 @@ function _reason(s) {
|
|
|
250
245
|
if (s.length > MAX_REASON_LEN) {
|
|
251
246
|
throw new TypeError("carrier-accounts: reason must be <= " + MAX_REASON_LEN + " characters");
|
|
252
247
|
}
|
|
253
|
-
|
|
254
|
-
throw new TypeError("carrier-accounts: reason contains control / zero-width bytes");
|
|
255
|
-
}
|
|
248
|
+
textGuard.freeText(s, "carrier-accounts: reason", { singleLine: "reject", zeroWidth: "reject" });
|
|
256
249
|
return s;
|
|
257
250
|
}
|
|
258
251
|
|
package/lib/catalog-drafts.js
CHANGED
|
@@ -106,6 +106,7 @@ var MAX_CHANGES_PER_DRAFT = 5000;
|
|
|
106
106
|
|
|
107
107
|
// Framework handle (the vendored blamejs); index.js re-exports this as .framework.
|
|
108
108
|
var b = require("./vendor/blamejs");
|
|
109
|
+
var textGuard = require("./text-guard");
|
|
109
110
|
|
|
110
111
|
var ROLLBACK_WINDOW_MS = b.constants.TIME.days(7);
|
|
111
112
|
|
|
@@ -125,10 +126,6 @@ var PRODUCT_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,198}[a-z0-9])?$/;
|
|
|
125
126
|
// to operator-authored fields — refuse control bytes + zero-width /
|
|
126
127
|
// direction-override codepoints so a rollback_reason / cancel_reason
|
|
127
128
|
// can't smuggle UI-confusing glyphs into the audit dashboard.
|
|
128
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
129
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
130
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
131
|
-
);
|
|
132
129
|
|
|
133
130
|
|
|
134
131
|
// ---- validators ---------------------------------------------------------
|
|
@@ -144,7 +141,7 @@ function _title(s, label) {
|
|
|
144
141
|
if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
|
|
145
142
|
throw new TypeError("catalogDrafts: " + label + " must be a non-empty string <= " + MAX_TITLE_LEN + " chars");
|
|
146
143
|
}
|
|
147
|
-
if (
|
|
144
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
148
145
|
throw new TypeError("catalogDrafts: " + label + " must not contain control bytes");
|
|
149
146
|
}
|
|
150
147
|
return s;
|
|
@@ -155,9 +152,7 @@ function _description(s) {
|
|
|
155
152
|
if (typeof s !== "string" || s.length > MAX_DESC_LEN) {
|
|
156
153
|
throw new TypeError("catalogDrafts: description must be a string <= " + MAX_DESC_LEN + " chars when provided");
|
|
157
154
|
}
|
|
158
|
-
|
|
159
|
-
throw new TypeError("catalogDrafts: description must not contain control bytes");
|
|
160
|
-
}
|
|
155
|
+
textGuard.freeText(s, "catalogDrafts: description");
|
|
161
156
|
return s;
|
|
162
157
|
}
|
|
163
158
|
|
|
@@ -169,7 +164,7 @@ function _optReason(s, label) {
|
|
|
169
164
|
if (s.length > MAX_REASON_LEN) {
|
|
170
165
|
throw new TypeError("catalogDrafts: " + label + " must be <= " + MAX_REASON_LEN + " chars");
|
|
171
166
|
}
|
|
172
|
-
if (
|
|
167
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
173
168
|
throw new TypeError("catalogDrafts: " + label + " contains control / zero-width bytes");
|
|
174
169
|
}
|
|
175
170
|
return s;
|
|
@@ -187,9 +182,7 @@ function _optOwner(s) {
|
|
|
187
182
|
if (typeof s !== "string" || !s.length || s.length > MAX_OWNER_LEN) {
|
|
188
183
|
throw new TypeError("catalogDrafts: owner_id must be a non-empty string <= " + MAX_OWNER_LEN + " chars when provided");
|
|
189
184
|
}
|
|
190
|
-
|
|
191
|
-
throw new TypeError("catalogDrafts: owner_id must not contain control bytes");
|
|
192
|
-
}
|
|
185
|
+
textGuard.freeText(s, "catalogDrafts: owner_id");
|
|
193
186
|
return s;
|
|
194
187
|
}
|
|
195
188
|
|
|
@@ -111,13 +111,9 @@ var MAX_POPULAR_LIMIT = 100;
|
|
|
111
111
|
var MAX_REORDER_BATCH = 500;
|
|
112
112
|
|
|
113
113
|
var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
114
|
-
var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
|
|
115
|
-
var CONTROL_BYTE_BODY_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
116
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
117
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
118
|
-
);
|
|
119
114
|
|
|
120
115
|
var b = require("./vendor/blamejs");
|
|
116
|
+
var textGuard = require("./text-guard");
|
|
121
117
|
|
|
122
118
|
// ---- monotonic clock ---------------------------------------------------
|
|
123
119
|
//
|
|
@@ -161,12 +157,7 @@ function _title(s) {
|
|
|
161
157
|
if (s.length > MAX_TITLE_LEN) {
|
|
162
158
|
throw new TypeError("categoryNavigation: title must be <= " + MAX_TITLE_LEN + " characters");
|
|
163
159
|
}
|
|
164
|
-
|
|
165
|
-
throw new TypeError("categoryNavigation: title contains control bytes");
|
|
166
|
-
}
|
|
167
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
168
|
-
throw new TypeError("categoryNavigation: title contains zero-width / direction-override characters");
|
|
169
|
-
}
|
|
160
|
+
textGuard.freeText(s, "categoryNavigation: title", { singleLine: "reject", zeroWidth: "reject" });
|
|
170
161
|
return s;
|
|
171
162
|
}
|
|
172
163
|
|
|
@@ -178,12 +169,7 @@ function _description(s) {
|
|
|
178
169
|
if (s.length > MAX_DESCRIPTION_LEN) {
|
|
179
170
|
throw new TypeError("categoryNavigation: description must be <= " + MAX_DESCRIPTION_LEN + " characters");
|
|
180
171
|
}
|
|
181
|
-
|
|
182
|
-
throw new TypeError("categoryNavigation: description contains control bytes");
|
|
183
|
-
}
|
|
184
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
185
|
-
throw new TypeError("categoryNavigation: description contains zero-width / direction-override characters");
|
|
186
|
-
}
|
|
172
|
+
textGuard.freeText(s, "categoryNavigation: description", { zeroWidth: "reject" });
|
|
187
173
|
return s;
|
|
188
174
|
}
|
|
189
175
|
|
|
@@ -200,9 +186,7 @@ function _heroImageUrl(s) {
|
|
|
200
186
|
if (s.length > MAX_HERO_URL_LEN) {
|
|
201
187
|
throw new TypeError("categoryNavigation: hero_image_url must be <= " + MAX_HERO_URL_LEN + " characters");
|
|
202
188
|
}
|
|
203
|
-
|
|
204
|
-
throw new TypeError("categoryNavigation: hero_image_url contains control / zero-width bytes");
|
|
205
|
-
}
|
|
189
|
+
textGuard.freeText(s, "categoryNavigation: hero_image_url", { zeroWidth: "reject" });
|
|
206
190
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
207
191
|
if (s.length > 1 && s.charCodeAt(1) === 47) {
|
|
208
192
|
throw new TypeError("categoryNavigation: hero_image_url protocol-relative `//host/...` refused — use absolute https://");
|
package/lib/cms-blocks.js
CHANGED
|
@@ -116,16 +116,13 @@ var LOCALE_RE = /^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/;
|
|
|
116
116
|
|
|
117
117
|
// Refuse C0 control bytes + DEL in operator-authored bodies. LF / CR
|
|
118
118
|
// / tab are allowed — operators author multi-line Markdown.
|
|
119
|
-
var CONTROL_BYTE_BLOCK_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
120
119
|
|
|
121
120
|
// Zero-width / direction-override family. Spelled with \u-escapes
|
|
122
121
|
// so ESLint's no-irregular-whitespace stays happy. Same defence as
|
|
123
122
|
// the rest of the shop primitives.
|
|
124
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
125
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
126
|
-
);
|
|
127
123
|
|
|
128
124
|
var b = require("./vendor/blamejs");
|
|
125
|
+
var textGuard = require("./text-guard");
|
|
129
126
|
|
|
130
127
|
// ---- validators ---------------------------------------------------------
|
|
131
128
|
|
|
@@ -141,10 +138,10 @@ function _body(s, label) {
|
|
|
141
138
|
if (typeof s !== "string" || !s.length || s.length > MAX_BODY_LEN) {
|
|
142
139
|
throw new TypeError("cmsBlocks: " + label + " must be a non-empty string <= " + MAX_BODY_LEN + " chars");
|
|
143
140
|
}
|
|
144
|
-
if (
|
|
141
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
145
142
|
throw new TypeError("cmsBlocks: " + label + " contains control bytes");
|
|
146
143
|
}
|
|
147
|
-
if (
|
|
144
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
148
145
|
throw new TypeError("cmsBlocks: " + label + " contains zero-width / direction-override characters");
|
|
149
146
|
}
|
|
150
147
|
return s;
|
|
@@ -241,7 +238,7 @@ function _esc(s) {
|
|
|
241
238
|
|
|
242
239
|
function _safeLinkUrl(url) {
|
|
243
240
|
if (typeof url !== "string" || !url.length || url.length > 2048) return null;
|
|
244
|
-
if (
|
|
241
|
+
if (textGuard.hasCodepointThreat(url, { zeroWidth: "reject" })) return null;
|
|
245
242
|
if (url.charCodeAt(0) === 47 /* "/" */) {
|
|
246
243
|
if (url.length > 1 && url.charCodeAt(1) === 47) return null;
|
|
247
244
|
if (url.indexOf("..") !== -1) return null;
|
package/lib/compliance-export.js
CHANGED
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
*/
|
|
115
115
|
|
|
116
116
|
var b = require("./vendor/blamejs");
|
|
117
|
+
var textGuard = require("./text-guard");
|
|
117
118
|
|
|
118
119
|
// ---- constants ----------------------------------------------------------
|
|
119
120
|
|
|
@@ -177,10 +178,6 @@ var DEFAULT_LIST_LIMIT = 50;
|
|
|
177
178
|
// Operator-authored prose lands in reason / dismiss_reason and
|
|
178
179
|
// replays into compliance review screens. Same control-byte +
|
|
179
180
|
// zero-width refusal posture the sibling primitives carry.
|
|
180
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
181
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
182
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
183
|
-
);
|
|
184
181
|
|
|
185
182
|
// Scope -> which injected readers contribute on export. Keeps the
|
|
186
183
|
// fulfillRequest logic a single lookup instead of nested if-else.
|
|
@@ -256,9 +253,7 @@ function _requestedBy(s) {
|
|
|
256
253
|
if (typeof s !== "string" || !s.length || s.length > MAX_REQUESTED_BY_LEN) {
|
|
257
254
|
throw new TypeError("complianceExport: requested_by must be a non-empty string <= " + MAX_REQUESTED_BY_LEN + " chars");
|
|
258
255
|
}
|
|
259
|
-
|
|
260
|
-
throw new TypeError("complianceExport: requested_by must not contain control bytes or zero-width characters");
|
|
261
|
-
}
|
|
256
|
+
textGuard.freeText(s, "complianceExport: requested_by", { zeroWidth: "reject" });
|
|
262
257
|
return s;
|
|
263
258
|
}
|
|
264
259
|
|
|
@@ -266,7 +261,7 @@ function _prose(s, label, maxLen) {
|
|
|
266
261
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
267
262
|
throw new TypeError("complianceExport: " + label + " must be a non-empty string <= " + maxLen + " chars");
|
|
268
263
|
}
|
|
269
|
-
if (
|
|
264
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
270
265
|
throw new TypeError("complianceExport: " + label + " must not contain control bytes or zero-width characters");
|
|
271
266
|
}
|
|
272
267
|
return s;
|
|
@@ -286,9 +281,7 @@ function _deliveryAddress(s) {
|
|
|
286
281
|
if (typeof s !== "string" || !s.length || s.length > MAX_DELIVERY_ADDR_LEN) {
|
|
287
282
|
throw new TypeError("complianceExport: delivery_address must be a non-empty string <= " + MAX_DELIVERY_ADDR_LEN + " chars");
|
|
288
283
|
}
|
|
289
|
-
|
|
290
|
-
throw new TypeError("complianceExport: delivery_address must not contain control bytes or zero-width characters");
|
|
291
|
-
}
|
|
284
|
+
textGuard.freeText(s, "complianceExport: delivery_address", { zeroWidth: "reject" });
|
|
292
285
|
return s;
|
|
293
286
|
}
|
|
294
287
|
|
package/lib/consent-ledger.js
CHANGED
|
@@ -123,7 +123,6 @@ var JURISDICTION_RE = /^[A-Z]{2}$/;
|
|
|
123
123
|
var EVIDENCE_REF_MAX_LEN = 256;
|
|
124
124
|
var EVIDENCE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
125
125
|
|
|
126
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
127
126
|
|
|
128
127
|
var CSV_COLUMNS = Object.freeze([
|
|
129
128
|
"id",
|
|
@@ -138,6 +137,7 @@ var CSV_COLUMNS = Object.freeze([
|
|
|
138
137
|
]);
|
|
139
138
|
|
|
140
139
|
var b = require("./vendor/blamejs");
|
|
140
|
+
var textGuard = require("./text-guard");
|
|
141
141
|
|
|
142
142
|
// The GDPR Art. 6(1) lawful bases, sourced from the framework's
|
|
143
143
|
// consent primitive so this ledger's vocabulary tracks the
|
|
@@ -269,9 +269,7 @@ function _optEvidenceRef(s) {
|
|
|
269
269
|
"consentLedger: evidence_ref must be <= " + EVIDENCE_REF_MAX_LEN + " characters"
|
|
270
270
|
);
|
|
271
271
|
}
|
|
272
|
-
|
|
273
|
-
throw new TypeError("consentLedger: evidence_ref must not contain control bytes");
|
|
274
|
-
}
|
|
272
|
+
textGuard.freeText(s, "consentLedger: evidence_ref", { singleLine: "reject" });
|
|
275
273
|
if (!EVIDENCE_REF_RE.test(s)) {
|
|
276
274
|
throw new TypeError(
|
|
277
275
|
"consentLedger: evidence_ref must match /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/"
|
package/lib/cost-layers.js
CHANGED
|
@@ -108,6 +108,16 @@ var b = require("./vendor/blamejs");
|
|
|
108
108
|
|
|
109
109
|
var SKU_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
110
110
|
var CURRENCY_RE = /^[A-Z]{3}$/;
|
|
111
|
+
|
|
112
|
+
// Carrier for the largest-remainder split of a line's COGS across the layers
|
|
113
|
+
// it drew from (see consumeForSale). b.money.allocate distributes a count of
|
|
114
|
+
// MINOR UNITS, so the currency is a type tag and not an input to the result —
|
|
115
|
+
// the split of 10040 across 40/40/20 is 4016/4016/2008 whether the money is
|
|
116
|
+
// 2-decimal, 0-decimal or this. It is deliberately NOT the layers' currency:
|
|
117
|
+
// receipts accept any well-formed ISO 4217 code while b.money accepts a
|
|
118
|
+
// narrower catalog, and pricing layers in one outside it would otherwise make
|
|
119
|
+
// them receivable but never sellable.
|
|
120
|
+
var ALLOCATION_CARRIER = "USD";
|
|
111
121
|
var METHODS = Object.freeze(["fifo", "lifo", "weighted_average"]);
|
|
112
122
|
var RECEIPT_SOURCES = Object.freeze(["receipt", "transfer", "adjustment"]);
|
|
113
123
|
// Internal source value used by `recordReversal` only; operators
|
|
@@ -453,11 +463,6 @@ function create(opts) {
|
|
|
453
463
|
// don't accumulate a fractional debt.
|
|
454
464
|
var lineNumerator = sumValue * quantity;
|
|
455
465
|
var lineCogs = Math.floor(lineNumerator / sumQty);
|
|
456
|
-
// Residual carried forward to per-layer attribution rows so
|
|
457
|
-
// the sum of attributions matches lineCogs exactly. The first
|
|
458
|
-
// layer in the iteration absorbs the residual (smallest
|
|
459
|
-
// possible deviation, deterministic placement).
|
|
460
|
-
var residual = lineCogs;
|
|
461
466
|
// Debit every layer proportionally. Use integer division so
|
|
462
467
|
// each layer's debit is an integer; spread any leftover
|
|
463
468
|
// single units across the earliest layers (deterministic).
|
|
@@ -491,20 +496,45 @@ function create(opts) {
|
|
|
491
496
|
remainingToConsume -= 1;
|
|
492
497
|
}
|
|
493
498
|
}
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
499
|
+
// Split the line's COGS across the debits in proportion to the units
|
|
500
|
+
// each one contributes. b.money.allocate distributes by largest
|
|
501
|
+
// remainder: every row lands within a single minor unit of its exact
|
|
502
|
+
// share, and the parts sum to the total with nothing left over.
|
|
503
|
+
//
|
|
504
|
+
// What it replaces priced each debit at the weighted-average unit cost
|
|
505
|
+
// ROUNDED to an integer, then handed the last debit whatever remained.
|
|
506
|
+
// Rounding the average absorbed a fraction of a minor unit per unit
|
|
507
|
+
// debited, and every one of those fractions landed on the final row.
|
|
508
|
+
// Consuming 40 + 40 + 20 units at an average of 100.4 attributed
|
|
509
|
+
// 4000 / 4000 / 2040 where the true shares are 4016 / 4016 / 2008 — a
|
|
510
|
+
// 32-minor-unit misattribution from one sale, growing with the
|
|
511
|
+
// quantities ahead of the last debit. The line total was always right,
|
|
512
|
+
// so nothing was over- or under-charged; it was the per-layer COGS the
|
|
513
|
+
// margin and valuation reports read that was wrong.
|
|
514
|
+
//
|
|
515
|
+
// The split is carried out on a Money in ALLOCATION_CARRIER rather than
|
|
516
|
+
// in the layers' own currency, and the currency genuinely does not
|
|
517
|
+
// enter the arithmetic: allocate distributes a count of minor units by
|
|
518
|
+
// largest remainder, so the exponent never participates. The identical
|
|
519
|
+
// split for a 2-decimal, a 0-decimal and this carrier is asserted in
|
|
520
|
+
// cost-layers.test.js so the assumption cannot rot silently.
|
|
521
|
+
//
|
|
522
|
+
// Using the layers' own currency would read better and is what this
|
|
523
|
+
// wanted to do. It cannot: b.money accepts a deliberately narrow
|
|
524
|
+
// catalog of ISO 4217 codes, while recordReceipt accepts any
|
|
525
|
+
// well-formed one. Pricing layers in a code outside that catalog is
|
|
526
|
+
// accepted today, and passing it here would throw on every
|
|
527
|
+
// weighted-average sale — turning a rounding inaccuracy into inventory
|
|
528
|
+
// that can be received but never sold. Asked upstream for an
|
|
529
|
+
// integer-level allocation in blamejs/blamejs#578.
|
|
530
|
+
var attrShares = debits.length
|
|
531
|
+
? b.money.fromMinorUnits(BigInt(lineCogs), ALLOCATION_CARRIER)
|
|
532
|
+
.allocate(debits.map(function (dd) { return dd.qty; }))
|
|
533
|
+
.map(function (m) { return Number(m.toMinorUnits()); })
|
|
534
|
+
: [];
|
|
498
535
|
for (var e = 0; e < debits.length; e += 1) {
|
|
499
536
|
var dRow = debits[e];
|
|
500
|
-
var attrCogs;
|
|
501
|
-
if (e === debits.length - 1) {
|
|
502
|
-
attrCogs = residual;
|
|
503
|
-
} else {
|
|
504
|
-
attrCogs = dRow.qty * avgUnitCost;
|
|
505
|
-
if (attrCogs > residual) attrCogs = residual;
|
|
506
|
-
residual -= attrCogs;
|
|
507
|
-
}
|
|
537
|
+
var attrCogs = attrShares[e];
|
|
508
538
|
// Attribute the debit's cost so `qty * unit_cost_minor` summed over
|
|
509
539
|
// its attribution rows reconstructs attrCogs EXACTLY — the COGS
|
|
510
540
|
// reports (cogsForOrder / cogsForPeriod) recover each line's cost
|
|
@@ -515,10 +545,15 @@ function create(opts) {
|
|
|
515
545
|
// (qty - r) units at floor, r units at floor+1, where
|
|
516
546
|
// r = attrCogs - floor*qty. (qty-r)*floor + r*(floor+1) =
|
|
517
547
|
// qty*floor + r = attrCogs — no penny drift between the sale's COGS
|
|
518
|
-
// and the persisted attributions (
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
548
|
+
// and the persisted attributions (a single rounded per-unit price
|
|
549
|
+
// drifts by up to qty-1 minor units on the row that carries the
|
|
550
|
+
// remainder).
|
|
551
|
+
//
|
|
552
|
+
// A debit is only recorded when its share is at least one unit (see
|
|
553
|
+
// the push above), so the division below always has a positive
|
|
554
|
+
// divisor.
|
|
555
|
+
var floorUnit = Math.floor(attrCogs / dRow.qty);
|
|
556
|
+
var rem = attrCogs - (floorUnit * dRow.qty);
|
|
522
557
|
var parts = [];
|
|
523
558
|
if (dRow.qty - rem > 0) parts.push({ qty: dRow.qty - rem, unit_cost_minor: floorUnit });
|
|
524
559
|
if (rem > 0) parts.push({ qty: rem, unit_cost_minor: floorUnit + 1 });
|
package/lib/customer-import.js
CHANGED
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
var b = require("./vendor/blamejs");
|
|
53
|
+
var textGuard = require("./text-guard");
|
|
53
54
|
|
|
54
55
|
var DEFAULT_MAX_ROWS = 200000;
|
|
55
56
|
var DEFAULT_MAX_ERRORS = 1000;
|
|
56
57
|
var SOURCES = Object.freeze(["csv", "ndjson", "api"]);
|
|
57
58
|
var ON_CONFLICT_MODES = Object.freeze(["update", "skip", "error"]);
|
|
58
59
|
var CSV_EXPECTED_HEADERS = Object.freeze(["email", "display_name"]);
|
|
59
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
60
60
|
var MAX_DISPLAY_NAME_LEN = 128;
|
|
61
61
|
|
|
62
62
|
// ---- factory ------------------------------------------------------------
|
|
@@ -143,7 +143,7 @@ function _normalizeRow(raw, rowIndex) {
|
|
|
143
143
|
if (displayName.length > MAX_DISPLAY_NAME_LEN) {
|
|
144
144
|
throw new TypeError("row " + rowIndex + ": display_name must be <= " + MAX_DISPLAY_NAME_LEN + " characters");
|
|
145
145
|
}
|
|
146
|
-
if (
|
|
146
|
+
if (textGuard.hasCodepointThreat(displayName)) {
|
|
147
147
|
throw new TypeError("row " + rowIndex + ": display_name contains control bytes");
|
|
148
148
|
}
|
|
149
149
|
// guardEmail strict profile — refuses smuggling / header-injection /
|
package/lib/customer-notes.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
48
|
var b = require("./vendor/blamejs");
|
|
49
|
+
var textGuard = require("./text-guard");
|
|
49
50
|
|
|
50
51
|
// ---- constants ----------------------------------------------------------
|
|
51
52
|
|
|
@@ -63,17 +64,9 @@ var MAX_LIMIT = 200;
|
|
|
63
64
|
var DEFAULT_TAG_LIMIT = 20;
|
|
64
65
|
var MAX_TAG_FETCH = 100;
|
|
65
66
|
|
|
66
|
-
//
|
|
67
|
-
// + tab survive in `body` so
|
|
68
|
-
// tags + ids are single-line and refuse
|
|
69
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
70
|
-
var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
|
|
71
|
-
// Zero-width / direction-override / BOM family. Spelled with
|
|
72
|
-
// \u-escapes so the source file stays free of irregular-whitespace
|
|
73
|
-
// ESLint hits.
|
|
74
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
75
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
76
|
-
);
|
|
67
|
+
// Control bytes, bidi overrides and the zero-width family are screened by
|
|
68
|
+
// textGuard.freeText. CR/LF + tab survive in `body` so an operator can wrap a
|
|
69
|
+
// multi-line note; tags + ids are single-line and refuse them.
|
|
77
70
|
|
|
78
71
|
// Tag shape — lowercase alnum + dash + underscore. The operator UI
|
|
79
72
|
// canonicalises on input (lower-case) so the popularTags aggregate
|
|
@@ -110,9 +103,7 @@ function _customerId(s) {
|
|
|
110
103
|
if (typeof s !== "string" || !s.length || s.length > MAX_CUSTOMER_ID) {
|
|
111
104
|
throw new TypeError("customerNotes: customer_id must be a non-empty string (<= " + MAX_CUSTOMER_ID + " chars)");
|
|
112
105
|
}
|
|
113
|
-
|
|
114
|
-
throw new TypeError("customerNotes: customer_id must not contain control bytes");
|
|
115
|
-
}
|
|
106
|
+
textGuard.freeText(s, "customerNotes: customer_id", { singleLine: "reject" });
|
|
116
107
|
return s;
|
|
117
108
|
}
|
|
118
109
|
|
|
@@ -128,9 +119,7 @@ function _authorId(s) {
|
|
|
128
119
|
if (typeof s !== "string" || !s.length || s.length > MAX_AUTHOR_ID) {
|
|
129
120
|
throw new TypeError("customerNotes: author_id must be a non-empty string (<= " + MAX_AUTHOR_ID + " chars) when provided");
|
|
130
121
|
}
|
|
131
|
-
|
|
132
|
-
throw new TypeError("customerNotes: author_id must not contain control bytes");
|
|
133
|
-
}
|
|
122
|
+
textGuard.freeText(s, "customerNotes: author_id", { singleLine: "reject" });
|
|
134
123
|
return s;
|
|
135
124
|
}
|
|
136
125
|
|
|
@@ -153,12 +142,7 @@ function _body(s) {
|
|
|
153
142
|
if (s.length > MAX_BODY_LEN) {
|
|
154
143
|
throw new TypeError("customerNotes: body must be <= " + MAX_BODY_LEN + " characters");
|
|
155
144
|
}
|
|
156
|
-
|
|
157
|
-
throw new TypeError("customerNotes: body must not contain control bytes");
|
|
158
|
-
}
|
|
159
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
160
|
-
throw new TypeError("customerNotes: body must not contain zero-width / direction-override characters");
|
|
161
|
-
}
|
|
145
|
+
textGuard.freeText(s, "customerNotes: body", { zeroWidth: "reject" });
|
|
162
146
|
return s;
|
|
163
147
|
}
|
|
164
148
|
|