Skip to content

Commit 0206e04

Browse files
JohnMcLearclaude
andauthored
checkPlugin: flag absolute /static/plugins/ asset paths in templates (#5203) (#7535)
* checkPlugin: flag absolute /static/plugins/ paths in templates (#5203) Plugin templates that reference assets as \`/static/plugins/...\` (absolute) silently break any Etherpad instance hosted behind a reverse proxy at a sub-path — the browser resolves the path against the domain root instead of the proxy prefix and the asset 404s. The right form is \`../static/plugins/...\` (relative), which ep_embedmedia PR #4 fixed manually and which #5203 asked for as a mechanical check. Walk \`templates/\` and \`static/\` of the plugin, scan every \`*.ejs\` / \`*.html\` for \`/static/plugins/\` not preceded by a URL scheme, dot, or word char (so \`https://host/static/plugins/...\` and already-correct \`../static/plugins/...\` stay untouched). Warn normally; in \`autofix\` mode rewrite to the relative form in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * checkPlugin: skip autofix for static/*.html Addresses Qodo review: HTML served from a plugin's static/ directory resolves against /static/plugins/<plugin>/static/..., so rewriting /static/plugins/... to ../static/plugins/... yields a broken URL. Keep scanning static/ for warnings but no longer rewrite, and clarify the remediation guidance to point at the file's own location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d62ab65 commit 0206e04

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

bin/plugins/checkPlugin.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,56 @@ log4js.configure({
374374
'Translation files help with Etherpad accessibility.');
375375
}
376376

377+
// Check template files for absolute `/static/plugins/...` asset paths.
378+
// Those break any Etherpad instance hosted behind a reverse proxy at a
379+
// sub-path (e.g. https://example.com/etherpad/pad) because the browser
380+
// resolves them against the domain root instead of the proxy prefix.
381+
// See #5203, and ep_embedmedia#4 for the original fix this check is modelled on.
382+
//
383+
// Autofix only rewrites paths in `templates/` (rendered under `/p/<pad>/...`
384+
// where `../static/plugins/...` is correct). Files under `static/` are
385+
// served from `/static/plugins/<plugin>/static/...` and need a different
386+
// relative prefix that depends on the file's depth, so we only warn.
387+
const STATIC_ABS = /(?<![./:\w])\/static\/plugins\//g;
388+
const scanDir = async (dir: 'templates' | 'static') => {
389+
const abs = `${pluginPath}/${dir}`;
390+
if (!files.includes(dir)) return;
391+
const scanFiles: string[] = [];
392+
const walk = async (d: string) => {
393+
for (const entry of await fsp.readdir(d, {withFileTypes: true})) {
394+
const full = `${d}/${entry.name}`;
395+
if (entry.isDirectory()) {
396+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
397+
await walk(full);
398+
} else if (/\.(ejs|html)$/.test(entry.name)) {
399+
scanFiles.push(full);
400+
}
401+
}
402+
};
403+
await walk(abs);
404+
for (const fp of scanFiles) {
405+
const src = await fsp.readFile(fp, 'utf8');
406+
if (!STATIC_ABS.test(src)) continue;
407+
STATIC_ABS.lastIndex = 0;
408+
const rel = path.relative(pluginPath, fp);
409+
if (dir === 'templates') {
410+
logger.warn(`${rel} contains absolute '/static/plugins/...' asset paths; ` +
411+
'these break reverse-proxied Etherpad deployments. Use ' +
412+
"'../static/plugins/...' instead.");
413+
if (autoFix) {
414+
logger.info(`Autofixing absolute /static/plugins/ paths in ${rel}`);
415+
await fsp.writeFile(fp, src.replace(STATIC_ABS, '../static/plugins/'));
416+
}
417+
} else {
418+
logger.warn(`${rel} contains absolute '/static/plugins/...' asset paths; ` +
419+
'these break reverse-proxied Etherpad deployments. Use a path ' +
420+
"relative to this file's location under 'static/' (no leading '/').");
421+
}
422+
}
423+
};
424+
await scanDir('templates');
425+
await scanDir('static');
426+
377427

378428
if (files.includes('.ep_initialized')) {
379429
logger.warn(

0 commit comments

Comments
 (0)