block. // Missing a formatter reduces presentation; it never hides the content. // // OPERATIONAL SAFEGUARDS // Paths are contained to SCRIPTS_DIR. // $rel is resolved with realpath() and required to remain under SCRIPTS_DIR, be a // regular file, and carry a .md extension. This is deliberate defence for a parameter // that will arrive from a request the moment this is wired up — without it, a // traversal sequence reaches any file the web user can read. // // Markdown is rendered in safe mode. // Parsedown runs with setSafeMode(true), and the
fallback escapes everything.
// These files are trusted today, but they are also synced between hosts.
//
// Substituted conf values are escaped.
// htmlspecialchars() is applied to both the value and the variable name, so a conf
// value containing markup cannot inject into the rendered page.
//
// Read-only. Discovers and renders; never writes a doc.
//
// EXPORTS
// vv_docs_tree() every *.md under SCRIPTS_DIR, relative paths, sorted
// vv_docs_render() one file to HTML with conf substitution applied
//
// CONFIGURATION
// SCRIPTS_DIR the containment root and the discovery root
// PARSEDOWN_PATH /usr/local/emhttp/plugins/varaverk/lib/Parsedown.php — optional
// ═══════════════════════════════════════════════════════════════════════════════════════════════
require_once __DIR__ . '/config.php';
define('PARSEDOWN_PATH', '/usr/local/emhttp/plugins/varaverk/lib/Parsedown.php');
function vv_docs_tree(): array {
$base = SCRIPTS_DIR;
$tree = [];
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $f) {
if ($f->isFile() && strtolower($f->getExtension()) === 'md') {
$rel = ltrim(str_replace($base, '', $f->getPathname()), '/');
$tree[] = $rel;
}
}
sort($tree);
return $tree;
}
function vv_docs_render(string $rel, array $vars): string {
// Containment check — $rel is expected to come from a request parameter once this is
// wired to a page. Resolve it and require the result to stay inside SCRIPTS_DIR and to
// still be a .md file, so a traversal sequence cannot reach arbitrary files.
$base = realpath(SCRIPTS_DIR);
$path = realpath(SCRIPTS_DIR . '/' . $rel);
if ($base === false || $path === false) return 'File not found.
';
if (!str_starts_with($path, $base . '/')) return 'File not found.
';
if (strtolower(pathinfo($path, PATHINFO_EXTENSION)) !== 'md') return 'File not found.
';
if (!is_file($path)) return 'File not found.
';
$md = file_get_contents($path);
// Substitute `$VAR_NAME` markers with live conf values
$md = preg_replace_callback('/`\$([A-Z0-9_]+)`/', function($m) use ($vars) {
$key = $m[1];
return isset($vars[$key])
? '' . htmlspecialchars($vars[$key]) . ''
: '$' . htmlspecialchars($key) . '';
}, $md);
// Render markdown
if (file_exists(PARSEDOWN_PATH)) {
require_once PARSEDOWN_PATH;
$pd = new Parsedown();
$pd->setSafeMode(true);
return $pd->text($md);
}
// Fallback: plain preformatted text
return '' . htmlspecialchars($md) . '
';
}