Document the PHP api layer and fix what documenting it exposed
Writing down what each endpoint actually guarantees made the places it didn't obvious — shell arguments reaching a crontab or a bash -c unescaped, master.conf written without tmp+rename, and conf edits that could be saved without ever being parsed.
This commit is contained in:
@@ -1,4 +1,71 @@
|
||||
<?php
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// Contextual help for one script. Returns its header block plus any documentation sections
|
||||
// about it found in the README and Manual files — the info panel on the scheduler page.
|
||||
//
|
||||
// OPERATIONAL MODEL
|
||||
// Documentation is assembled at request time, not indexed. Two sources are combined: the
|
||||
// script's own header comment, which is authoritative because it lives next to the code,
|
||||
// and matching sections from the markdown docs, which give the surrounding context the
|
||||
// header deliberately leaves out.
|
||||
//
|
||||
// Four documents are searched per request — the top-level README.md and Manual.md, plus the
|
||||
// module-level README-<Dir>.md and Manual-<Dir>.md for the script's own folder. That mirrors
|
||||
// how the docs are actually organised: general behaviour at the top, specifics per folder.
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Matches headings by slug, with a first-word fallback.
|
||||
// The script name is normalised — underscores and hyphens to spaces, lowercased — and a
|
||||
// heading matches if it contains the whole slug or, failing that, the first word when
|
||||
// that word is longer than three characters. The length floor is what stops a script
|
||||
// beginning with "arr" or "sync" from matching every section in the file.
|
||||
//
|
||||
// Intro sections are never matched.
|
||||
// The matcher returns false for the intro, because the opening prose of a README
|
||||
// mentions many scripts and would otherwise match nearly all of them.
|
||||
//
|
||||
// Every section carries its source label.
|
||||
// The panel shows where each block came from, so a reader can tell the module manual
|
||||
// from the top-level README rather than seeing one undifferentiated wall of text.
|
||||
//
|
||||
// Works for documents as well as scripts.
|
||||
// A non-.sh id yields no header and only the doc sections, so the same endpoint serves
|
||||
// the docs tab's own entries.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// Read-only. Nothing here writes, executes, or schedules anything.
|
||||
//
|
||||
// Traversal is blocked before any path is composed.
|
||||
// An explicit '..' check plus ^[A-Za-z0-9_./\-]+$. The slash must be permitted because
|
||||
// ids are Category/name.sh, so the '..' test carries the traversal guarantee on its own.
|
||||
//
|
||||
// The filesystem is only touched for ids that name a script.
|
||||
// The header read is guarded by both the .sh suffix test and file_exists(), so a
|
||||
// well-formed id for a file that is not there returns an empty header rather than a
|
||||
// warning that would leak the absolute path into the JSON body.
|
||||
//
|
||||
// Every document is existence-checked before it is searched, and the module-level path is
|
||||
// additionally guarded against a dirname of '.' — an id with no directory component would
|
||||
// otherwise compose README-..md and search a file that cannot exist.
|
||||
//
|
||||
// Missing documentation is a normal outcome.
|
||||
// No header and no matching sections yields ok:true with empty values. A script nobody
|
||||
// has written about yet is not an error, and reporting it as one would put a failure in
|
||||
// the panel for most custom scripts.
|
||||
//
|
||||
// REQUEST
|
||||
// GET ?id=<Category/name.sh> or any documented id
|
||||
//
|
||||
// RESPONSE
|
||||
// {"ok":true,"name":"…","header":"…","sections":[{"source":"…","body":"…"}, …]}
|
||||
// {"ok":false,"error":"Invalid id"}
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/scheduler.php vv_script_header_clean()
|
||||
// include/docs.php vv_readme_section() (loaded transitively)
|
||||
// SCRIPTS_DIR README.md, Manual.md, README-<Dir>.md, Manual-<Dir>.md
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/scheduler.php';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user