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) . '
'; }