@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/seller-signup.js
CHANGED
|
@@ -129,12 +129,9 @@ var MIME_RE = /^[A-Za-z0-9!#$&^_.+\-]{1,127}\/[A-Za-z0-9!#$&^_
|
|
|
129
129
|
// the shape vendors / purchase-orders use. Operator-rendered fields
|
|
130
130
|
// refuse these to keep downstream dashboards / KYC printouts safe
|
|
131
131
|
// from header-injection + visual-spoofing attacks.
|
|
132
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
133
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
134
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
135
|
-
);
|
|
136
132
|
|
|
137
133
|
var b = require("./vendor/blamejs");
|
|
134
|
+
var textGuard = require("./text-guard");
|
|
138
135
|
|
|
139
136
|
// ---- validators ---------------------------------------------------------
|
|
140
137
|
|
|
@@ -157,12 +154,7 @@ function _businessName(s) {
|
|
|
157
154
|
if (s.length > MAX_BUSINESS_NAME_LEN) {
|
|
158
155
|
throw new TypeError("seller-signup: business_name must be <= " + MAX_BUSINESS_NAME_LEN + " characters");
|
|
159
156
|
}
|
|
160
|
-
|
|
161
|
-
throw new TypeError("seller-signup: business_name contains control bytes");
|
|
162
|
-
}
|
|
163
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
164
|
-
throw new TypeError("seller-signup: business_name contains zero-width / direction-override bytes");
|
|
165
|
-
}
|
|
157
|
+
textGuard.freeText(s, "seller-signup: business_name", { singleLine: "reject", zeroWidth: "reject" });
|
|
166
158
|
return s;
|
|
167
159
|
}
|
|
168
160
|
|
|
@@ -219,9 +211,7 @@ function _businessAddress(value) {
|
|
|
219
211
|
if (encoded.length > MAX_ADDRESS_JSON_LEN) {
|
|
220
212
|
throw new TypeError("seller-signup: business_address JSON must be <= " + MAX_ADDRESS_JSON_LEN + " characters serialised");
|
|
221
213
|
}
|
|
222
|
-
|
|
223
|
-
throw new TypeError("seller-signup: business_address contains control / zero-width bytes");
|
|
224
|
-
}
|
|
214
|
+
textGuard.freeText(encoded, "seller-signup: business_address", { singleLine: "reject", zeroWidth: "reject" });
|
|
225
215
|
return encoded;
|
|
226
216
|
}
|
|
227
217
|
|
|
@@ -260,7 +250,7 @@ function _categoryFocus(value) {
|
|
|
260
250
|
if (typeof value[i] !== "string" || !value[i].length) {
|
|
261
251
|
throw new TypeError("seller-signup: category_focus[" + i + "] must be a non-empty string");
|
|
262
252
|
}
|
|
263
|
-
if (
|
|
253
|
+
if (textGuard.hasCodepointThreat(value[i], { singleLine: "reject", zeroWidth: "reject" })) {
|
|
264
254
|
throw new TypeError("seller-signup: category_focus[" + i + "] contains control / zero-width bytes");
|
|
265
255
|
}
|
|
266
256
|
}
|
|
@@ -305,9 +295,7 @@ function _referencesJson(value) {
|
|
|
305
295
|
if (encoded.length > MAX_REFERENCES_JSON_LEN) {
|
|
306
296
|
throw new TypeError("seller-signup: references_json must be <= " + MAX_REFERENCES_JSON_LEN + " characters serialised");
|
|
307
297
|
}
|
|
308
|
-
|
|
309
|
-
throw new TypeError("seller-signup: references_json contains control / zero-width bytes");
|
|
310
|
-
}
|
|
298
|
+
textGuard.freeText(encoded, "seller-signup: references_json", { singleLine: "reject", zeroWidth: "reject" });
|
|
311
299
|
return encoded;
|
|
312
300
|
}
|
|
313
301
|
|
|
@@ -325,12 +313,10 @@ function _instructions(s) {
|
|
|
325
313
|
if (s.length > MAX_INSTRUCTIONS_LEN) {
|
|
326
314
|
throw new TypeError("seller-signup: instructions must be <= " + MAX_INSTRUCTIONS_LEN + " characters");
|
|
327
315
|
}
|
|
328
|
-
if (
|
|
316
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
329
317
|
throw new TypeError("seller-signup: instructions contains control bytes");
|
|
330
318
|
}
|
|
331
|
-
|
|
332
|
-
throw new TypeError("seller-signup: instructions contains zero-width / direction-override bytes");
|
|
333
|
-
}
|
|
319
|
+
textGuard.freeText(s, "seller-signup: instructions", { zeroWidth: "reject" });
|
|
334
320
|
return s;
|
|
335
321
|
}
|
|
336
322
|
|
|
@@ -341,12 +327,10 @@ function _rejectReason(s) {
|
|
|
341
327
|
if (s.length > MAX_REJECT_REASON_LEN) {
|
|
342
328
|
throw new TypeError("seller-signup: reason must be <= " + MAX_REJECT_REASON_LEN + " characters");
|
|
343
329
|
}
|
|
344
|
-
if (
|
|
330
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
345
331
|
throw new TypeError("seller-signup: reason contains control bytes");
|
|
346
332
|
}
|
|
347
|
-
|
|
348
|
-
throw new TypeError("seller-signup: reason contains zero-width / direction-override bytes");
|
|
349
|
-
}
|
|
333
|
+
textGuard.freeText(s, "seller-signup: reason", { zeroWidth: "reject" });
|
|
350
334
|
return s;
|
|
351
335
|
}
|
|
352
336
|
|
|
@@ -357,7 +341,7 @@ function _operatorId(s, label) {
|
|
|
357
341
|
if (s.length > MAX_OPERATOR_ID_LEN) {
|
|
358
342
|
throw new TypeError("seller-signup: " + label + " must be <= " + MAX_OPERATOR_ID_LEN + " characters");
|
|
359
343
|
}
|
|
360
|
-
if (
|
|
344
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
|
|
361
345
|
throw new TypeError("seller-signup: " + label + " contains control / zero-width bytes");
|
|
362
346
|
}
|
|
363
347
|
return s;
|
package/lib/sidebar-widgets.js
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
*/
|
|
76
76
|
|
|
77
77
|
var b = require("./vendor/blamejs");
|
|
78
|
+
var textGuard = require("./text-guard");
|
|
78
79
|
|
|
79
80
|
// ---- constants ----------------------------------------------------------
|
|
80
81
|
|
|
@@ -125,11 +126,6 @@ var PAGE_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9:._\/-]{0,119}$/;
|
|
|
125
126
|
var BADGE_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
126
127
|
var SEGMENT_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
127
128
|
|
|
128
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
129
|
-
var CONTROL_BYTE_BLOCK_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
130
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
131
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
132
|
-
);
|
|
133
129
|
|
|
134
130
|
var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
135
131
|
"title",
|
|
@@ -176,12 +172,7 @@ function _title(s) {
|
|
|
176
172
|
if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
|
|
177
173
|
throw new TypeError("sidebarWidgets: title must be a non-empty string <= " + MAX_TITLE_LEN + " chars");
|
|
178
174
|
}
|
|
179
|
-
|
|
180
|
-
throw new TypeError("sidebarWidgets: title must not contain control bytes (incl. CR/LF)");
|
|
181
|
-
}
|
|
182
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
183
|
-
throw new TypeError("sidebarWidgets: title must not contain zero-width / direction-override characters");
|
|
184
|
-
}
|
|
175
|
+
textGuard.freeText(s, "sidebarWidgets: title", { singleLine: "reject", zeroWidth: "reject" });
|
|
185
176
|
return s;
|
|
186
177
|
}
|
|
187
178
|
|
|
@@ -260,10 +251,10 @@ function _line(s, label, maxLen) {
|
|
|
260
251
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
261
252
|
throw new TypeError("sidebarWidgets: " + label + " must be a non-empty string <= " + maxLen + " chars");
|
|
262
253
|
}
|
|
263
|
-
if (
|
|
254
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
264
255
|
throw new TypeError("sidebarWidgets: " + label + " must not contain control bytes (incl. CR/LF)");
|
|
265
256
|
}
|
|
266
|
-
if (
|
|
257
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
267
258
|
throw new TypeError("sidebarWidgets: " + label + " must not contain zero-width / direction-override characters");
|
|
268
259
|
}
|
|
269
260
|
return s;
|
|
@@ -273,10 +264,10 @@ function _block(s, label, maxLen) {
|
|
|
273
264
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
274
265
|
throw new TypeError("sidebarWidgets: " + label + " must be a non-empty string <= " + maxLen + " chars");
|
|
275
266
|
}
|
|
276
|
-
if (
|
|
267
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
277
268
|
throw new TypeError("sidebarWidgets: " + label + " must not contain control bytes");
|
|
278
269
|
}
|
|
279
|
-
if (
|
|
270
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
280
271
|
throw new TypeError("sidebarWidgets: " + label + " must not contain zero-width / direction-override characters");
|
|
281
272
|
}
|
|
282
273
|
return s;
|
package/lib/site-redirects.js
CHANGED
|
@@ -101,18 +101,15 @@ var MAX_REGEX_SOURCE_LEN = 512;
|
|
|
101
101
|
var MAX_LIMIT = 1000;
|
|
102
102
|
var DEFAULT_LIMIT = 100;
|
|
103
103
|
|
|
104
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
105
104
|
// Zero-width + invisible bytes: U+200B (zero-width space),
|
|
106
105
|
// U+200C (zero-width non-joiner), U+200D (zero-width joiner),
|
|
107
106
|
// U+2060 (word joiner), U+FEFF (zero-width no-break space / BOM).
|
|
108
107
|
// Source code uses escape sequences so the file itself stays
|
|
109
108
|
// linter-clean (raw zero-width bytes in source trip both
|
|
110
109
|
// no-irregular-whitespace and no-misleading-character-class).
|
|
111
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
112
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
113
|
-
);
|
|
114
110
|
|
|
115
111
|
var b = require("./vendor/blamejs");
|
|
112
|
+
var textGuard = require("./text-guard");
|
|
116
113
|
|
|
117
114
|
// Monotonic clock — ensures consecutive _now() calls from the same
|
|
118
115
|
// process never collide in the same millisecond, so updated_at /
|
|
@@ -153,9 +150,7 @@ function _sourcePath(s) {
|
|
|
153
150
|
"siteRedirects: source_path must be <= " + MAX_SOURCE_PATH_LEN + " characters"
|
|
154
151
|
);
|
|
155
152
|
}
|
|
156
|
-
|
|
157
|
-
throw new TypeError("siteRedirects: source_path contains control / zero-width bytes");
|
|
158
|
-
}
|
|
153
|
+
textGuard.freeText(s, "siteRedirects: source_path", { singleLine: "reject", zeroWidth: "reject" });
|
|
159
154
|
if (s.charCodeAt(0) !== 47 /* "/" */) {
|
|
160
155
|
throw new TypeError("siteRedirects: source_path must be /-rooted (start with '/')");
|
|
161
156
|
}
|
|
@@ -179,9 +174,7 @@ function _targetUrl(s) {
|
|
|
179
174
|
"siteRedirects: target_url must be <= " + MAX_TARGET_URL_LEN + " characters"
|
|
180
175
|
);
|
|
181
176
|
}
|
|
182
|
-
|
|
183
|
-
throw new TypeError("siteRedirects: target_url contains control / zero-width bytes");
|
|
184
|
-
}
|
|
177
|
+
textGuard.freeText(s, "siteRedirects: target_url", { singleLine: "reject", zeroWidth: "reject" });
|
|
185
178
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
186
179
|
if (s.length > 1 && s.charCodeAt(1) === 47) {
|
|
187
180
|
throw new TypeError(
|
|
@@ -240,18 +233,29 @@ function _epochMs(n, label) {
|
|
|
240
233
|
return n;
|
|
241
234
|
}
|
|
242
235
|
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
// pattern
|
|
236
|
+
// Compile an operator's redirect pattern, anchored with `^` and `$` so
|
|
237
|
+
// `resolveForPath` answers an exact-path match rather than a substring one
|
|
238
|
+
// (`/foo` must not find `/foo/bar`).
|
|
239
|
+
//
|
|
240
|
+
// The pattern is matched by b.regexLinear, which simulates every path through
|
|
241
|
+
// it one character at a time, so the work cannot grow faster than the subject
|
|
242
|
+
// times the pattern no matter what the operator wrote. That replaces the
|
|
243
|
+
// screen this used to run — a walker for backreferences and lookaround, then
|
|
244
|
+
// b.guardRegex for backtracking shapes.
|
|
250
245
|
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
246
|
+
// Screening was always an approximation in both directions: it refused
|
|
247
|
+
// patterns that were perfectly linear, and it could not promise to catch every
|
|
248
|
+
// shape that was not, so each shape it missed became another case to add. The
|
|
249
|
+
// patterns it existed to stop — `(a+)+`, `(a|a)*` — are now simply run: on two
|
|
250
|
+
// thousand characters they finish in about two milliseconds where the platform
|
|
251
|
+
// engine takes the better part of a minute. An operator gets their pattern
|
|
252
|
+
// accepted and the request path stays bounded, which is what the screen was
|
|
253
|
+
// trying and failing to deliver.
|
|
254
|
+
//
|
|
255
|
+
// Three constructs still cannot be simulated this way and are refused by name:
|
|
256
|
+
// a backreference, a lookaround, and a repetition whose body can match nothing.
|
|
257
|
+
// Each is mapped below into this module's TypeError envelope so a caller sees
|
|
258
|
+
// one error shape.
|
|
255
259
|
function _compileRegex(source) {
|
|
256
260
|
if (typeof source !== "string" || !source.length) {
|
|
257
261
|
throw new TypeError("siteRedirects: source_path (regex) must be a non-empty string");
|
|
@@ -261,127 +265,37 @@ function _compileRegex(source) {
|
|
|
261
265
|
"siteRedirects: regex source must be <= " + MAX_REGEX_SOURCE_LEN + " characters"
|
|
262
266
|
);
|
|
263
267
|
}
|
|
264
|
-
|
|
265
|
-
throw new TypeError("siteRedirects: regex source contains control / zero-width bytes");
|
|
266
|
-
}
|
|
267
|
-
// Backreferences: \1 .. \9 outside a character class. The walker
|
|
268
|
-
// tracks character-class depth so `[\1]` (literal byte) doesn't
|
|
269
|
-
// trip the gate, but `(.)\1` (a real backreference) does.
|
|
270
|
-
var inClass = 0;
|
|
271
|
-
for (var i = 0; i < source.length; i += 1) {
|
|
272
|
-
var ch = source.charAt(i);
|
|
273
|
-
if (ch === "\\" && i + 1 < source.length) {
|
|
274
|
-
var nxt = source.charAt(i + 1);
|
|
275
|
-
if (inClass === 0 && nxt >= "1" && nxt <= "9") {
|
|
276
|
-
throw new TypeError(
|
|
277
|
-
"siteRedirects: regex backreferences (\\1..\\9) refused — operators register " +
|
|
278
|
-
"separate redirect rows for each variant instead"
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
i += 1; // skip escaped char
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (ch === "[") { inClass += 1; continue; }
|
|
285
|
-
if (ch === "]" && inClass > 0) { inClass -= 1; continue; }
|
|
286
|
-
if (inClass > 0) continue;
|
|
287
|
-
if (ch === "(" && i + 2 < source.length && source.charAt(i + 1) === "?") {
|
|
288
|
-
var third = source.charAt(i + 2);
|
|
289
|
-
if (third === "=" || third === "!" || third === "<") {
|
|
290
|
-
throw new TypeError(
|
|
291
|
-
"siteRedirects: regex lookahead / lookbehind (`(?=`, `(?!`, `(?<=`, `(?<!`) refused " +
|
|
292
|
-
"— catastrophic-backtracking surface; rewrite as `prefix` or split into multiple rows"
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// Build the anchored form. RegExp() throws SyntaxError on a
|
|
298
|
-
// malformed source — re-shape that into the primitive's TypeError
|
|
299
|
-
// envelope so callers can match on the consistent error shape.
|
|
268
|
+
textGuard.freeText(source, "siteRedirects: regex source", { singleLine: "reject", zeroWidth: "reject" });
|
|
300
269
|
var anchored = "^(?:" + source + ")$";
|
|
301
|
-
var compiled;
|
|
302
270
|
try {
|
|
303
|
-
|
|
271
|
+
return b.regexLinear.compile(anchored);
|
|
304
272
|
} catch (e) {
|
|
273
|
+
var code = (e && e.code) || "";
|
|
274
|
+
if (code === "regex/unsupported-backreference") {
|
|
275
|
+
throw new TypeError(
|
|
276
|
+
"siteRedirects: regex backreferences (\\1..\\9) refused — a backreference " +
|
|
277
|
+
"cannot be matched in linear time; register a separate redirect row for " +
|
|
278
|
+
"each variant instead"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (code === "regex/unsupported-lookaround") {
|
|
282
|
+
throw new TypeError(
|
|
283
|
+
"siteRedirects: regex lookahead / lookbehind (`(?=`, `(?!`, `(?<=`, `(?<!`) " +
|
|
284
|
+
"refused — lookaround cannot be matched in linear time; rewrite as `prefix` " +
|
|
285
|
+
"or split into multiple rows"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (code === "regex/nullable-repetition") {
|
|
289
|
+
throw new TypeError(
|
|
290
|
+
"siteRedirects: a repetition whose body can match nothing (`(a*)*`) is " +
|
|
291
|
+
"refused — it matches exactly what its body matches, so write that instead"
|
|
292
|
+
);
|
|
293
|
+
}
|
|
305
294
|
throw new TypeError(
|
|
306
295
|
"siteRedirects: regex source did not compile — " +
|
|
307
296
|
(e && e.message ? e.message : "invalid pattern")
|
|
308
297
|
);
|
|
309
298
|
}
|
|
310
|
-
// Extend the backreference / lookaround screen above with the last
|
|
311
|
-
// catastrophic-backtracking class it doesn't cover: a nested unbounded
|
|
312
|
-
// quantifier ((a+)+, (a*)*, (?:a+)+, (.+)+ …). This regex runs against the
|
|
313
|
-
// request path in `resolveForPath`, so a super-linear pattern is a DoS on
|
|
314
|
-
// every matching request. (The framework's b.guardRegex screen is not used
|
|
315
|
-
// here because its text scan misreads the `?` in a `(?:…)` group prefix and
|
|
316
|
-
// a bounded trailing `?` as quantifiers, rejecting linear patterns like
|
|
317
|
-
// `/blog(?:/page/\d+)?` — filed upstream; this walker allows them.)
|
|
318
|
-
if (_hasNestedUnboundedQuantifier(source)) {
|
|
319
|
-
throw new TypeError(
|
|
320
|
-
"siteRedirects: regex source rejected — nested unbounded quantifier " +
|
|
321
|
-
"(catastrophic-backtracking / ReDoS) surface; rewrite as `prefix` / " +
|
|
322
|
-
"`exact`, or split into multiple rows"
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
return compiled;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Detect the nested-unbounded-quantifier ReDoS class: a group whose body
|
|
329
|
-
// contains an unbounded quantifier (`+`, `*`, `{n,}`) and is itself repeated
|
|
330
|
-
// by an unbounded quantifier — `(a+)+`, `(a*)*`, `(?:a+)+`, `(.+)+`,
|
|
331
|
-
// `((ab)+)+` at any depth. A single optional / linearly-repeated group
|
|
332
|
-
// (`(?:foo)?`, `(?:bar)*`, `/products/.+`) is safe and MUST be allowed. The
|
|
333
|
-
// walk mirrors the backreference/lookaround walk: char-by-char, honoring
|
|
334
|
-
// escapes, character classes, and the `(?:` / `(?<name>` group prefixes whose
|
|
335
|
-
// `?` is group syntax, not a quantifier. Lookaround is already refused before
|
|
336
|
-
// this runs, so only `(?:` reaches here.
|
|
337
|
-
function _hasNestedUnboundedQuantifier(source) {
|
|
338
|
-
var stack = []; // open groups: { bodyUnbounded: bool }
|
|
339
|
-
var inClass = false;
|
|
340
|
-
var i = 0;
|
|
341
|
-
var n = source.length;
|
|
342
|
-
function outerUnboundedAt(idx) {
|
|
343
|
-
var c = source.charAt(idx);
|
|
344
|
-
if (c === "*" || c === "+") return true;
|
|
345
|
-
if (c === "{") { return /^\{\d*,\}/.test(source.slice(idx)); } // {n,} — no upper bound
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
while (i < n) {
|
|
349
|
-
var ch = source.charAt(i);
|
|
350
|
-
if (ch === "\\") { i += 2; continue; } // escaped atom
|
|
351
|
-
if (inClass) { if (ch === "]") { inClass = false; } i += 1; continue; }
|
|
352
|
-
if (ch === "[") { inClass = true; i += 1; continue; }
|
|
353
|
-
if (ch === "(") {
|
|
354
|
-
if (source.charAt(i + 1) === "?") { // group prefix — not a quantifier
|
|
355
|
-
i += 2;
|
|
356
|
-
if (source.charAt(i) === ":") { i += 1; }
|
|
357
|
-
else if (source.charAt(i) === "<") { while (i < n && source.charAt(i) !== ">") { i += 1; } if (i < n) { i += 1; } }
|
|
358
|
-
} else { i += 1; }
|
|
359
|
-
stack.push({ bodyUnbounded: false });
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (ch === ")") {
|
|
363
|
-
var g = stack.pop() || { bodyUnbounded: false };
|
|
364
|
-
var outer = outerUnboundedAt(i + 1);
|
|
365
|
-
if (g.bodyUnbounded && outer) { return true; } // nested unbounded quantifier
|
|
366
|
-
// A group repeated by an unbounded quantifier is itself an unbounded
|
|
367
|
-
// atom for its parent.
|
|
368
|
-
if (outer && stack.length) { stack[stack.length - 1].bodyUnbounded = true; }
|
|
369
|
-
i += 1;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (ch === "*" || ch === "+") {
|
|
373
|
-
if (stack.length) { stack[stack.length - 1].bodyUnbounded = true; }
|
|
374
|
-
i += 1;
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
if (ch === "{" && /^\{\d*,\}/.test(source.slice(i))) {
|
|
378
|
-
if (stack.length) { stack[stack.length - 1].bodyUnbounded = true; }
|
|
379
|
-
i += 1;
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
i += 1;
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
299
|
}
|
|
386
300
|
|
|
387
301
|
// ---- row hydration ------------------------------------------------------
|
|
@@ -577,9 +491,7 @@ function create(opts) {
|
|
|
577
491
|
if (typeof path !== "string" || !path.length) {
|
|
578
492
|
throw new TypeError("siteRedirects.resolveForPath: path must be a non-empty string");
|
|
579
493
|
}
|
|
580
|
-
|
|
581
|
-
throw new TypeError("siteRedirects.resolveForPath: path contains control bytes");
|
|
582
|
-
}
|
|
494
|
+
textGuard.freeText(path, "siteRedirects.resolveForPath: path", { singleLine: "reject" });
|
|
583
495
|
var now = _now();
|
|
584
496
|
|
|
585
497
|
// -- exact ----------------------------------------------------------
|
package/lib/sitemap-generator.js
CHANGED
|
@@ -107,9 +107,9 @@ var ALLOWED_CHANGEFREQS = Object.freeze([
|
|
|
107
107
|
"never",
|
|
108
108
|
]);
|
|
109
109
|
|
|
110
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
111
110
|
|
|
112
111
|
var b = require("./vendor/blamejs");
|
|
112
|
+
var textGuard = require("./text-guard");
|
|
113
113
|
|
|
114
114
|
// ---- validators ---------------------------------------------------------
|
|
115
115
|
|
|
@@ -134,9 +134,7 @@ function _basePath(s) {
|
|
|
134
134
|
if (typeof s !== "string" || s.length < 1 || s.length > MAX_BASE_PATH_LEN) {
|
|
135
135
|
throw new TypeError("sitemapGenerator: base_path must be a string 1.." + MAX_BASE_PATH_LEN + " chars");
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
throw new TypeError("sitemapGenerator: base_path contains control bytes");
|
|
139
|
-
}
|
|
137
|
+
textGuard.freeText(s, "sitemapGenerator: base_path", { singleLine: "reject" });
|
|
140
138
|
if (s.charCodeAt(0) !== 47 /* "/" */) {
|
|
141
139
|
throw new TypeError("sitemapGenerator: base_path must start with '/'");
|
|
142
140
|
}
|
|
@@ -177,9 +175,7 @@ function _originUrl(u) {
|
|
|
177
175
|
if (typeof u !== "string" || u.length < 1 || u.length > MAX_ORIGIN_URL_LEN) {
|
|
178
176
|
throw new TypeError("sitemapGenerator: origin_url must be a string 1.." + MAX_ORIGIN_URL_LEN + " chars");
|
|
179
177
|
}
|
|
180
|
-
|
|
181
|
-
throw new TypeError("sitemapGenerator: origin_url contains control bytes");
|
|
182
|
-
}
|
|
178
|
+
textGuard.freeText(u, "sitemapGenerator: origin_url", { singleLine: "reject" });
|
|
183
179
|
try {
|
|
184
180
|
b.safeUrl.parse(u, { allowedProtocols: ["https:"] });
|
|
185
181
|
} catch (e) {
|
package/lib/sms-dispatcher.js
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
64
|
var b = require("./vendor/blamejs");
|
|
65
|
+
var textGuard = require("./text-guard");
|
|
65
66
|
var C = b.constants;
|
|
66
67
|
|
|
67
68
|
// ---- constants ----------------------------------------------------------
|
|
@@ -103,10 +104,6 @@ var SLUG_RE = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
|
103
104
|
var COUNTRY_RE = /^[A-Z]{2}$/;
|
|
104
105
|
var PHONE_HASH_RE = /^[0-9a-f]{128}$/;
|
|
105
106
|
var PHONE_NORM_RE = /^\+[1-9][0-9]{6,14}$/; // E.164: +<country><subscriber>, 7-15 digits total
|
|
106
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
107
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
108
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
109
|
-
);
|
|
110
107
|
|
|
111
108
|
// Inbound STOP-keyword detection. Operators in the US/UK/AU regulatory
|
|
112
109
|
// envelope have all converged on this short list; the primitive
|
|
@@ -150,9 +147,7 @@ function _name(s) {
|
|
|
150
147
|
if (s.length > MAX_NAME_LEN) {
|
|
151
148
|
throw new TypeError("smsDispatcher: name must be <= " + MAX_NAME_LEN + " characters");
|
|
152
149
|
}
|
|
153
|
-
|
|
154
|
-
throw new TypeError("smsDispatcher: name contains control / zero-width bytes");
|
|
155
|
-
}
|
|
150
|
+
textGuard.freeText(s, "smsDispatcher: name", { zeroWidth: "reject" });
|
|
156
151
|
return s;
|
|
157
152
|
}
|
|
158
153
|
|
|
@@ -163,9 +158,7 @@ function _endpointUrl(s) {
|
|
|
163
158
|
if (s.length > MAX_URL_LEN) {
|
|
164
159
|
throw new TypeError("smsDispatcher: endpoint_url must be <= " + MAX_URL_LEN + " characters");
|
|
165
160
|
}
|
|
166
|
-
|
|
167
|
-
throw new TypeError("smsDispatcher: endpoint_url contains control / zero-width bytes");
|
|
168
|
-
}
|
|
161
|
+
textGuard.freeText(s, "smsDispatcher: endpoint_url", { zeroWidth: "reject" });
|
|
169
162
|
// Shape gate — refuse anything that isn't an absolute https URL.
|
|
170
163
|
// Provider endpoints are public APIs; operators using a non-https
|
|
171
164
|
// egress are leaking the message body across the wire.
|
|
@@ -260,7 +253,7 @@ function _reason(s, label) {
|
|
|
260
253
|
if (s.length > MAX_REASON_LEN) {
|
|
261
254
|
throw new TypeError("smsDispatcher: " + label + " must be <= " + MAX_REASON_LEN + " characters");
|
|
262
255
|
}
|
|
263
|
-
if (
|
|
256
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
264
257
|
throw new TypeError("smsDispatcher: " + label + " contains control / zero-width bytes");
|
|
265
258
|
}
|
|
266
259
|
return s;
|
|
@@ -274,9 +267,7 @@ function _optInText(s) {
|
|
|
274
267
|
if (s.length > MAX_OPT_IN_TEXT_LEN) {
|
|
275
268
|
throw new TypeError("smsDispatcher: opt_in_text must be <= " + MAX_OPT_IN_TEXT_LEN + " characters");
|
|
276
269
|
}
|
|
277
|
-
|
|
278
|
-
throw new TypeError("smsDispatcher: opt_in_text contains control / zero-width bytes");
|
|
279
|
-
}
|
|
270
|
+
textGuard.freeText(s, "smsDispatcher: opt_in_text", { zeroWidth: "reject" });
|
|
280
271
|
return s;
|
|
281
272
|
}
|
|
282
273
|
|
|
@@ -287,9 +278,7 @@ function _providerRef(s) {
|
|
|
287
278
|
if (s.length > MAX_PROVIDER_REF_LEN) {
|
|
288
279
|
throw new TypeError("smsDispatcher: provider_ref must be <= " + MAX_PROVIDER_REF_LEN + " characters");
|
|
289
280
|
}
|
|
290
|
-
|
|
291
|
-
throw new TypeError("smsDispatcher: provider_ref contains control / zero-width bytes");
|
|
292
|
-
}
|
|
281
|
+
textGuard.freeText(s, "smsDispatcher: provider_ref", { zeroWidth: "reject" });
|
|
293
282
|
return s;
|
|
294
283
|
}
|
|
295
284
|
|
|
@@ -333,12 +322,7 @@ function _body(s) {
|
|
|
333
322
|
if (s.length > MAX_BODY_LEN) {
|
|
334
323
|
throw new TypeError("smsDispatcher: body must be <= " + MAX_BODY_LEN + " characters");
|
|
335
324
|
}
|
|
336
|
-
|
|
337
|
-
throw new TypeError("smsDispatcher: body contains control bytes");
|
|
338
|
-
}
|
|
339
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
340
|
-
throw new TypeError("smsDispatcher: body contains zero-width / bidi-override codepoints");
|
|
341
|
-
}
|
|
325
|
+
textGuard.freeText(s, "smsDispatcher: body", { zeroWidth: "reject" });
|
|
342
326
|
// Refuse the degenerate emoji-only shape. Walk the string by code
|
|
343
327
|
// point so surrogate pairs in the SMP emoji blocks count as one
|
|
344
328
|
// character.
|
package/lib/stock-receipts.js
CHANGED
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
*/
|
|
71
71
|
|
|
72
72
|
var b = require("./vendor/blamejs");
|
|
73
|
+
var textGuard = require("./text-guard");
|
|
73
74
|
var C = b.constants;
|
|
74
75
|
|
|
75
76
|
// ---- constants ----------------------------------------------------------
|
|
@@ -100,7 +101,6 @@ var LINE_STATES = Object.freeze([
|
|
|
100
101
|
"pending", "received", "damaged", "partial",
|
|
101
102
|
]);
|
|
102
103
|
|
|
103
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
104
104
|
|
|
105
105
|
// ---- monotonic clock ----------------------------------------------------
|
|
106
106
|
//
|
|
@@ -171,9 +171,7 @@ function _reason(s) {
|
|
|
171
171
|
if (typeof s !== "string" || !s.length || s.length > MAX_REASON_LEN) {
|
|
172
172
|
throw new TypeError("stockReceipts: reason must be a non-empty string <= " + MAX_REASON_LEN + " chars");
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
throw new TypeError("stockReceipts: reason must not contain control bytes");
|
|
176
|
-
}
|
|
174
|
+
textGuard.freeText(s, "stockReceipts: reason");
|
|
177
175
|
return s;
|
|
178
176
|
}
|
|
179
177
|
|
|
@@ -192,9 +190,7 @@ function _uaOpt(s) {
|
|
|
192
190
|
if (typeof s !== "string" || s.length > MAX_UA_LEN) {
|
|
193
191
|
throw new TypeError("stockReceipts: user_agent must be a string <= " + MAX_UA_LEN + " chars");
|
|
194
192
|
}
|
|
195
|
-
|
|
196
|
-
throw new TypeError("stockReceipts: user_agent must not contain control bytes");
|
|
197
|
-
}
|
|
193
|
+
textGuard.freeText(s, "stockReceipts: user_agent");
|
|
198
194
|
return s;
|
|
199
195
|
}
|
|
200
196
|
|
|
@@ -203,9 +199,7 @@ function _ipOpt(s) {
|
|
|
203
199
|
if (typeof s !== "string" || s.length > MAX_IP_LEN) {
|
|
204
200
|
throw new TypeError("stockReceipts: client_ip must be a string <= " + MAX_IP_LEN + " chars");
|
|
205
201
|
}
|
|
206
|
-
|
|
207
|
-
throw new TypeError("stockReceipts: client_ip must not contain control bytes");
|
|
208
|
-
}
|
|
202
|
+
textGuard.freeText(s, "stockReceipts: client_ip");
|
|
209
203
|
return s;
|
|
210
204
|
}
|
|
211
205
|
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
*/
|
|
90
90
|
|
|
91
91
|
var b = require("./vendor/blamejs");
|
|
92
|
+
var textGuard = require("./text-guard");
|
|
92
93
|
|
|
93
94
|
var MAX_SLUG_LEN = 80;
|
|
94
95
|
var MAX_TITLE_LEN = 200;
|
|
@@ -140,14 +141,10 @@ var WIDGET_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
|
140
141
|
var LOCALE_RE = /^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/;
|
|
141
142
|
|
|
142
143
|
// Refuse C0 control bytes + DEL in operator-authored text fields.
|
|
143
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
144
144
|
|
|
145
145
|
// Zero-width / direction-override family — mirrors the rest of the
|
|
146
146
|
// shop primitives. Spelled with \u-escapes so ESLint's
|
|
147
147
|
// no-irregular-whitespace stays happy.
|
|
148
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
149
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
150
|
-
);
|
|
151
148
|
|
|
152
149
|
// ---- validators ---------------------------------------------------------
|
|
153
150
|
|
|
@@ -166,12 +163,7 @@ function _title(s) {
|
|
|
166
163
|
if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
|
|
167
164
|
throw new TypeError("storefrontDashboards: title must be a non-empty string <= " + MAX_TITLE_LEN + " chars");
|
|
168
165
|
}
|
|
169
|
-
|
|
170
|
-
throw new TypeError("storefrontDashboards: title contains control bytes (incl. CR/LF)");
|
|
171
|
-
}
|
|
172
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
173
|
-
throw new TypeError("storefrontDashboards: title contains zero-width / direction-override characters");
|
|
174
|
-
}
|
|
166
|
+
textGuard.freeText(s, "storefrontDashboards: title", { singleLine: "reject", zeroWidth: "reject" });
|
|
175
167
|
return s;
|
|
176
168
|
}
|
|
177
169
|
|