shelving 1.267.5 → 1.267.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/util/template.js +13 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.267.5",
3
+ "version": "1.267.6",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/util/template.js CHANGED
@@ -217,14 +217,14 @@ export function renderTemplate(template, values, caller = renderTemplate) {
217
217
  let output = template;
218
218
  if (isFunction(values)) {
219
219
  for (const { name, placeholder } of chunks)
220
- output = output.replace(placeholder, values(name));
220
+ output = output.replace(placeholder, _escapeReplacement(values(name)));
221
221
  }
222
222
  else if (isData(values)) {
223
223
  for (const { name, placeholder } of chunks) {
224
224
  const v = getString(getDataProp(values, name));
225
225
  if (v === undefined)
226
226
  throw new RequiredError(`Template placeholder "${name}" not found in object`, { received: values, name, caller });
227
- output = output.replace(placeholder, v);
227
+ output = output.replace(placeholder, _escapeReplacement(v));
228
228
  }
229
229
  }
230
230
  else if (isArray(values)) {
@@ -232,7 +232,7 @@ export function renderTemplate(template, values, caller = renderTemplate) {
232
232
  const v = getString(values[Number(name)]);
233
233
  if (v === undefined)
234
234
  throw new RequiredError(`Template placeholder "${name}" not found in array`, { received: values, name, caller });
235
- output = output.replace(placeholder, v);
235
+ output = output.replace(placeholder, _escapeReplacement(v));
236
236
  }
237
237
  }
238
238
  else {
@@ -240,10 +240,19 @@ export function renderTemplate(template, values, caller = renderTemplate) {
240
240
  if (v === undefined)
241
241
  throw new RequiredError(`Template value must be string`, { received: values, caller });
242
242
  for (const { placeholder } of chunks)
243
- output = output.replace(placeholder, v);
243
+ output = output.replace(placeholder, _escapeReplacement(v));
244
244
  }
245
245
  return output;
246
246
  }
247
+ /**
248
+ * Escape `$` in a `String.prototype.replace()` replacement value so it's inserted literally.
249
+ * - Without this, `$&`, `$'`, `` $` ``, `$1`, `$$` in a (often user-supplied) value are interpreted as replacement
250
+ * patterns and pull surrounding template text into the value's slot.
251
+ * - Cheap: only rewrites when a `$` is actually present, so the common case is a single scan with no allocation.
252
+ */
253
+ function _escapeReplacement(value) {
254
+ return value.includes("$") ? value.replaceAll("$", "$$$$") : value;
255
+ }
247
256
  /**
248
257
  * Render a path-shaped template, percent-encoding each substituted value so it stays within its path segment.
249
258
  *