@normed/bundle 4.8.3 → 4.8.4

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.
@@ -68878,6 +68878,42 @@ function resolvePackagePath(packageSpecifier, fromFile) {
68878
68878
  return null;
68879
68879
  }
68880
68880
  }
68881
+ var packageRootCache = /* @__PURE__ */ new Map();
68882
+ function findPackageRoot(filePath) {
68883
+ let dir = path5.dirname(path5.resolve(filePath));
68884
+ const cacheKey = dir;
68885
+ if (packageRootCache.has(cacheKey)) return packageRootCache.get(cacheKey);
68886
+ while (true) {
68887
+ if (fs6.existsSync(path5.join(dir, "package.json"))) {
68888
+ packageRootCache.set(cacheKey, dir);
68889
+ return dir;
68890
+ }
68891
+ const parent = path5.dirname(dir);
68892
+ if (parent === dir) {
68893
+ packageRootCache.set(cacheKey, null);
68894
+ return null;
68895
+ }
68896
+ dir = parent;
68897
+ }
68898
+ }
68899
+ function resolveAssetPath(assetPath, pugDir, srcWebRoot) {
68900
+ if (assetPath.startsWith("/")) {
68901
+ return path5.resolve(srcWebRoot, assetPath.slice(1));
68902
+ }
68903
+ return path5.resolve(pugDir, assetPath);
68904
+ }
68905
+ function assertWithinAllowedRoots(absolutePath, allowedRoots, originalValue, pugFilePath) {
68906
+ const normalizedPath = path5.resolve(absolutePath);
68907
+ const withinARoot = allowedRoots.some((root) => {
68908
+ const normalizedRoot = path5.resolve(root);
68909
+ return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + path5.sep);
68910
+ });
68911
+ if (!withinARoot) {
68912
+ throw new Error(
68913
+ `Security: asset path "${originalValue}" resolves to "${normalizedPath}" which is outside the allowed source directories [${allowedRoots.join(", ")}]. Referenced in ${pugFilePath}. Use the copy: or build: prefix to resolve via Node module resolution.`
68914
+ );
68915
+ }
68916
+ }
68881
68917
  var discoveredPugReferences = /* @__PURE__ */ new Map();
68882
68918
  var discoveredLessReferences = /* @__PURE__ */ new Map();
68883
68919
  var discoveredScriptReferences = /* @__PURE__ */ new Map();
@@ -68912,7 +68948,7 @@ function computeHtmlAssetPath(opts) {
68912
68948
  }
68913
68949
  return path5.relative(opts.htmlOutputDir, opts.absoluteOutput);
68914
68950
  }
68915
- async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
68951
+ async function processHtmlAssets(html, pugFilePath, options2, webRoot, collectedReferences) {
68916
68952
  const assets = [];
68917
68953
  const pugReferences = [];
68918
68954
  const lessReferences = [];
@@ -68923,30 +68959,43 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
68923
68959
  const outbase = options2.outbase || path5.dirname(pugFilePath);
68924
68960
  const pugDir = path5.dirname(pugFilePath);
68925
68961
  const publicPath = options2.publicPath || "";
68926
- const processedAssets = /* @__PURE__ */ new Map();
68927
- const allAttrs = [...new Set(Object.values(ASSET_ATTRIBUTES).flat())].join(
68928
- "|"
68962
+ const srcWebRoot = webRoot ? path5.join(outbase, webRoot) : outbase;
68963
+ const normalizedOutbase = path5.resolve(outbase);
68964
+ const normalizedPug = path5.resolve(pugFilePath);
68965
+ const pugWithinOutbase = normalizedPug === normalizedOutbase || normalizedPug.startsWith(normalizedOutbase + path5.sep);
68966
+ const packageRoot = pugWithinOutbase ? null : findPackageRoot(pugFilePath);
68967
+ const allowedRoots = [outbase, packageRoot].filter(
68968
+ (r) => r != null
68929
68969
  );
68930
- const attrRegex = new RegExp(`(${allAttrs})\\s*=\\s*["']([^"']+)["']`, "gi");
68970
+ const processedAssets = /* @__PURE__ */ new Map();
68931
68971
  let modifiedHtml = html;
68932
68972
  const matches = [];
68933
- let match;
68934
- while ((match = attrRegex.exec(html)) !== null) {
68935
- const attr = match[1];
68936
- const value = match[2];
68937
- if (attr && value) {
68938
- matches.push({
68939
- fullMatch: match[0],
68940
- attr: attr.toLowerCase(),
68941
- value
68942
- });
68973
+ if (collectedReferences && collectedReferences.length > 0) {
68974
+ for (const ref of collectedReferences) {
68975
+ matches.push({ attr: ref.attr, value: ref.value });
68976
+ }
68977
+ } else {
68978
+ const allAttrs = [...new Set(Object.values(ASSET_ATTRIBUTES).flat())].join(
68979
+ "|"
68980
+ );
68981
+ const attrRegex = new RegExp(
68982
+ `(${allAttrs})\\s*=\\s*["']([^"']+)["']`,
68983
+ "gi"
68984
+ );
68985
+ let match;
68986
+ while ((match = attrRegex.exec(html)) !== null) {
68987
+ const attr = match[1];
68988
+ const value = match[2];
68989
+ if (attr && value) {
68990
+ matches.push({ attr: attr.toLowerCase(), value });
68991
+ }
68943
68992
  }
68944
68993
  }
68945
68994
  const discoveredPugPaths = /* @__PURE__ */ new Set();
68946
68995
  const discoveredLessPaths = /* @__PURE__ */ new Set();
68947
68996
  const discoveredScriptPaths = /* @__PURE__ */ new Set();
68948
68997
  const discoveredPackageCssPaths = /* @__PURE__ */ new Set();
68949
- for (const { fullMatch, attr, value } of matches) {
68998
+ for (const { attr, value } of matches) {
68950
68999
  let newValue = value;
68951
69000
  if (attr === "srcset") {
68952
69001
  const srcsetParts = value.split(",");
@@ -68959,7 +69008,13 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
68959
69008
  [getVerbatimPath(assetPath), ...descriptors].filter(Boolean).join(" ")
68960
69009
  );
68961
69010
  } else if (assetPath && isPugReference(assetPath)) {
68962
- const absolutePath = path5.resolve(pugDir, assetPath);
69011
+ const absolutePath = resolveAssetPath(assetPath, pugDir, srcWebRoot);
69012
+ assertWithinAllowedRoots(
69013
+ absolutePath,
69014
+ allowedRoots,
69015
+ assetPath,
69016
+ pugFilePath
69017
+ );
68963
69018
  if (!discoveredPugPaths.has(absolutePath)) {
68964
69019
  discoveredPugPaths.add(absolutePath);
68965
69020
  pugReferences.push({ originalHref: assetPath, absolutePath });
@@ -68976,6 +69031,8 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
68976
69031
  publicPath,
68977
69032
  assets,
68978
69033
  processedAssets,
69034
+ srcWebRoot,
69035
+ allowedRoots,
68979
69036
  webRoot
68980
69037
  );
68981
69038
  if (hashedPath) {
@@ -68993,19 +69050,22 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
68993
69050
  } else if (isVerbatimReference(value)) {
68994
69051
  newValue = getVerbatimPath(value);
68995
69052
  } else if (isPugReference(value)) {
68996
- const absolutePath = path5.resolve(pugDir, value);
69053
+ const absolutePath = resolveAssetPath(value, pugDir, srcWebRoot);
69054
+ assertWithinAllowedRoots(absolutePath, allowedRoots, value, pugFilePath);
68997
69055
  if (!discoveredPugPaths.has(absolutePath)) {
68998
69056
  discoveredPugPaths.add(absolutePath);
68999
69057
  pugReferences.push({ originalHref: value, absolutePath });
69000
69058
  }
69001
69059
  } else if (isLessReference(value)) {
69002
- const absolutePath = path5.resolve(pugDir, value);
69060
+ const absolutePath = resolveAssetPath(value, pugDir, srcWebRoot);
69061
+ assertWithinAllowedRoots(absolutePath, allowedRoots, value, pugFilePath);
69003
69062
  if (!discoveredLessPaths.has(absolutePath)) {
69004
69063
  discoveredLessPaths.add(absolutePath);
69005
69064
  lessReferences.push({ originalHref: value, absolutePath });
69006
69065
  }
69007
69066
  } else if (isScriptReference(value)) {
69008
- const absolutePath = path5.resolve(pugDir, value);
69067
+ const absolutePath = resolveAssetPath(value, pugDir, srcWebRoot);
69068
+ assertWithinAllowedRoots(absolutePath, allowedRoots, value, pugFilePath);
69009
69069
  if (!discoveredScriptPaths.has(absolutePath)) {
69010
69070
  discoveredScriptPaths.add(absolutePath);
69011
69071
  scriptReferences.push({ originalHref: value, absolutePath });
@@ -69137,6 +69197,8 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
69137
69197
  publicPath,
69138
69198
  assets,
69139
69199
  processedAssets,
69200
+ srcWebRoot,
69201
+ allowedRoots,
69140
69202
  webRoot
69141
69203
  );
69142
69204
  if (hashedPath) {
@@ -69144,8 +69206,9 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
69144
69206
  }
69145
69207
  }
69146
69208
  if (newValue !== value) {
69147
- const newFullMatch = fullMatch.replace(value, newValue);
69148
- modifiedHtml = modifiedHtml.replace(fullMatch, newFullMatch);
69209
+ const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69210
+ const valueRegex = new RegExp(`(["'])${escapedValue}\\1`);
69211
+ modifiedHtml = modifiedHtml.replace(valueRegex, `$1${newValue}$1`);
69149
69212
  }
69150
69213
  }
69151
69214
  return {
@@ -69157,8 +69220,14 @@ async function processHtmlAssets(html, pugFilePath, options2, webRoot) {
69157
69220
  packageCssReferences
69158
69221
  };
69159
69222
  }
69160
- async function processAsset(assetPath, pugDir, pugFilePath, outdir, outbase, assetNames, publicPath, assets, processedAssets, webRoot) {
69161
- const absoluteSource = path5.resolve(pugDir, assetPath);
69223
+ async function processAsset(assetPath, pugDir, pugFilePath, outdir, outbase, assetNames, publicPath, assets, processedAssets, srcWebRoot, allowedRoots, webRoot) {
69224
+ const absoluteSource = resolveAssetPath(assetPath, pugDir, srcWebRoot);
69225
+ assertWithinAllowedRoots(
69226
+ absoluteSource,
69227
+ allowedRoots,
69228
+ assetPath,
69229
+ pugFilePath
69230
+ );
69162
69231
  if (processedAssets.has(absoluteSource)) {
69163
69232
  return processedAssets.get(absoluteSource);
69164
69233
  }
@@ -69353,6 +69422,36 @@ function createRebaseAssetsPlugin(entryFilePath) {
69353
69422
  }
69354
69423
  };
69355
69424
  }
69425
+ function createAssetCollectorPlugin(collectedReferences) {
69426
+ return {
69427
+ preCodeGen(ast) {
69428
+ const pending = [...ast?.nodes || []];
69429
+ while (pending.length > 0) {
69430
+ const node = pending.shift();
69431
+ if (!node) continue;
69432
+ if (node.block?.nodes) {
69433
+ pending.push(...node.block.nodes);
69434
+ }
69435
+ if (node.nodes) {
69436
+ pending.push(...node.nodes);
69437
+ }
69438
+ const assetAttrs = ASSET_ATTRIBUTES[node.name];
69439
+ if (!assetAttrs) continue;
69440
+ if (!node.attrs) continue;
69441
+ for (const attr of node.attrs) {
69442
+ if (!assetAttrs.includes(attr.name)) continue;
69443
+ const extracted = extractStaticString(attr.val);
69444
+ if (!extracted) continue;
69445
+ collectedReferences.push({
69446
+ attr: attr.name,
69447
+ value: extracted.value
69448
+ });
69449
+ }
69450
+ }
69451
+ return ast;
69452
+ }
69453
+ };
69454
+ }
69356
69455
  async function loadAsText2(_filepath) {
69357
69456
  return {
69358
69457
  loader: "text"
@@ -69394,12 +69493,14 @@ async function loadAsHtml(filepath, options2) {
69394
69493
  }
69395
69494
  async function loadAsEntrypoint(filepath, options2, webRoot) {
69396
69495
  const fileData = await fs6.promises.readFile(filepath, "utf8");
69496
+ const collectedReferences = [];
69397
69497
  let contents = import_pug.default.render(fileData, {
69398
69498
  filename: filepath,
69399
69499
  basedir: options2.outbase,
69400
69500
  name: "render",
69401
69501
  plugins: [
69402
69502
  createRebaseAssetsPlugin(filepath),
69503
+ createAssetCollectorPlugin(collectedReferences),
69403
69504
  pugTransformer(isScriptNodeWithSrcAttr, (nodes) => {
69404
69505
  nodes;
69405
69506
  })
@@ -69413,7 +69514,13 @@ async function loadAsEntrypoint(filepath, options2, webRoot) {
69413
69514
  lessReferences,
69414
69515
  scriptReferences,
69415
69516
  packageCssReferences
69416
- } = await processHtmlAssets(contents, filepath, options2, webRoot);
69517
+ } = await processHtmlAssets(
69518
+ contents,
69519
+ filepath,
69520
+ options2,
69521
+ webRoot,
69522
+ collectedReferences
69523
+ );
69417
69524
  contents = processedHtml;
69418
69525
  if (pugReferences.length > 0) {
69419
69526
  discoveredPugReferences.set(filepath, pugReferences);