feat: Betrachter fuer Lesbares, und aus dem <video> wird ein Player

Bild, Video, Ton und PDF konnte der Betrachter schon. Was fehlte, war
ausgerechnet die Art, die man am ehesten nur lesen und nicht herunterladen
will: eine .md landete im Zweig "laesst sich hier nicht anzeigen".

LESBARES. Markdown gesetzt, .lua und .json eingefaerbt, CSV als Tabelle, alles
Uebrige mit Zeilennummern. Der Tokenizer ist DERSELBE wie fuer die Schnipsel
auf der API-Seite und nicht ein zweiter -- zwei waeren zwei, die auseinander
laufen. Die Zeilennummern liegen in einer eigenen Spalte: so nimmt ein
Markieren mit der Maus sie nicht mit.

Die CSV-Zerlegung kann Anfuehrungszeichen und doppelte darin. Das ist der Teil,
den ein split(';') falsch macht, und CSV aus Excel hat ihn regelmaessig; das
Trennzeichen wird aus der Kopfzeile geraten.

NICHT ALLES WIRD GEHOLT. Die Groesse steht in der Datenbank, also wissen wir
vorher, worauf wir uns einlassen: ueber 512 KB kommt per Bereichsanfrage nur
der Anfang, und das steht auch da. Ein Betrachter, der bei einem 400-MB-
Protokoll den Tab abschiesst, ist schlimmer als einer, der die Datei gar nicht
erst oeffnet. Gemessen: Range: bytes=0-49 -> 206, 50 Bytes.

MARKDOWN OHNE BIBLIOTHEK, und das ist kein Geiz. Der uebliche Weg heisst marked
plus DOMPurify und endet bei dangerouslySetInnerHTML -- damit haengt die
Sicherheit dieser Seite an der Frage, ob die Filterliste vollstaendig ist. Hier
entsteht NIE eine HTML-Zeichenkette: der Text wird zu React-Knoten, und ein
<script> in einer hochgeladenen .md erscheint als die acht Zeichen, die es ist.
Im ganzen ui/ steht kein einziges dangerouslySetInnerHTML.

Die eine Luecke, die React nicht schliesst, ist href: [klick](javascript:...)
kaeme durch. Zwoelf Faelle in Node durchgeprueft, alle bestanden -- http,
https, mailto und relative Ziele durch; javascript:, JavaScript:, mit
Leerzeichen davor, data:, vbscript:, file: abgelehnt und als Text stehen
gelassen.

DER PLAYER. Die Bedienelemente bleiben die des Browsers: ein selbstgebauter
Schieber sieht in jedem Browser anders falsch aus, kennt keine
Tastatursteuerung und keine Untertitel. Drumherum kam, was der Browser nicht
mitbringt.

Lautstaerke ueber Dateien hinweg -- <video> setzt sie bei jedem neuen Element
auf 1 zurueck, und wer sich durch zwanzig Clips klickt, stellt sie sonst
zwanzigmal leise, waehrend der einundzwanzigste ungefragt wieder hochfaehrt.

Weiter zum naechsten Stueck, abschaltbar, und nur wenn das Naechste auch etwas
zum Abspielen ist: nach einem Lied ungefragt bei einem Bild zu landen waere
schlechter als stehenzubleiben.

Tastatur an EINER Stelle: Leertaste haelt an, Pfeile springen fuenf Sekunden,
Umschalt+Pfeil wechselt die Datei. Bei allem anderen blaettern die Pfeile wie
bisher. Dass das so ist, steht als Zeile unter dem Bild -- eine unsichtbare
Sonderregel fuehlt sich an wie ein Fehler.

SERVER. Die Typtabelle kennt jetzt md, markdown, csv, lua, xml, yml, yaml,
toml, ini, cfg, sql und log -- alle als text/plain. Nicht als text/markdown
(braechte nichts) und erst recht nicht als text/html: wer einen Upload-Token
hat, koennte damit eine Seite unter unserem Namen veroeffentlichen. html, css
und js stehen deshalb weiterhin NICHT drin.

Der Betrachter entscheidet trotzdem nach der Endung und erst danach nach der
gemeldeten Art. Grund steht in der Datenbank: items/readme.md lag schon da und
trug application/octet-stream, weil sie vor der erweiterten Tabelle hochgeladen
wurde. Wer nur auf die Art schaut, zeigt die alte README nicht an und eine neue
schon -- und sucht den Unterschied an der falschen Stelle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 21:58:10 +02:00
co-authored by Claude Opus 5
parent aa820c32af
commit 7b35ef5698
9 changed files with 862 additions and 30 deletions
+55
View File
@@ -546,6 +546,61 @@ ABI-Nummer verloren. Grenzen: 5000 Einträge, 256 MB entpackt, geprüft **vor**
dem Entpacken (Zip-Bombe). Zip-Slip nachgemessen: `../../../../etc/passwd` und
`..\..\windows\hosts` abgelehnt, die harmlose Datei im selben Archiv abgelegt.
### ✅ Fertig — Betrachter für Lesbares, und ein richtiger Player
Bild, Video, Ton und PDF konnte der Betrachter schon. Was fehlte, war
ausgerechnet die Art, die man am ehesten nur lesen will: eine `.md` landete im
Zweig „lässt sich hier nicht anzeigen".
**Jetzt lesbar:** Markdown gesetzt, `.lua`/`.json` eingefärbt (derselbe
Tokenizer wie auf der API-Seite, nicht ein zweiter), CSV als Tabelle — mit
Anführungszeichen und doppelten darin, das ist der Teil, den ein
`split(';')` falsch macht —, alles Übrige mit Zeilennummern in einer eigenen
Spalte, damit ein Kopieren sie nicht mitnimmt.
**Nicht alles wird geholt.** Die Größe steht in der Datenbank, also wissen wir
vorher, worauf wir uns einlassen: über 512 KB kommt per Bereichsanfrage nur der
Anfang, und das steht auch da. Gemessen: `Range: bytes=0-49``206`, 50 Bytes.
**Markdown ohne Bibliothek.** `marked` + `DOMPurify` enden bei
`dangerouslySetInnerHTML`, und die Sicherheit hängt dann an der Frage, ob die
Filterliste vollständig ist. Hier entsteht **nie eine HTML-Zeichenkette**
Text wird zu React-Knoten. Im ganzen `ui/` steht kein einziges
`dangerouslySetInnerHTML` (außer in den zwei Kommentarzeilen, die erklären,
warum).
Die eine Lücke, die React nicht schließt, ist `href`: `[klick](javascript:…)`
käme durch. Zwölf Fälle in Node durchgeprüft, alle bestanden — `http`, `https`,
`mailto` und relative Ziele durch; `javascript:`, `JavaScript:`, mit
Leerzeichen davor, `data:`, `vbscript:`, `file:` abgelehnt und als Text stehen
gelassen.
**Der Player.** Die Bedienelemente bleiben die des Browsers — ein selbstgebauter
Schieber sieht in jedem Browser anders falsch aus und kann keine Tastatur.
Drumherum kam, was der Browser nicht mitbringt:
- **Lautstärke über Dateien hinweg.** `<video>` setzt sie bei jedem neuen
Element auf 1 zurück; wer zwanzig Clips durchsieht, stellt sie sonst
zwanzigmal leise — und beim einundzwanzigsten fährt sie ungefragt hoch.
- **Weiter zum nächsten Stück**, abschaltbar. Und nur, wenn das Nächste auch
etwas zum Abspielen ist: nach einem Lied ungefragt bei einem Bild zu landen
wäre schlechter als stehenzubleiben.
- **Tastatur:** Leertaste hält an, ←/→ springen fünf Sekunden, Umschalt+←/→
wechselt die Datei. Bei allem anderen blättern die Pfeile wie bisher. Die
Belegung steht als Zeile unter dem Bild — eine unsichtbare Sonderregel fühlt
sich an wie ein Fehler.
**Server:** die Typtabelle kennt jetzt `md`, `csv`, `lua`, `xml`, `yml`, `toml`,
`ini`, `cfg`, `sql`, `log`**alle als `text/plain`**. Nicht als `text/html`,
und `html`/`css`/`js` stehen weiterhin *nicht* drin: wer einen Upload-Token
hat, könnte sonst eine Seite unter unserem Namen veröffentlichen.
Der Betrachter entscheidet trotzdem nach der **Endung** und erst danach nach der
gemeldeten Art. Grund: `items/readme.md` lag schon da und trug
`application/octet-stream`, weil sie vor der erweiterten Tabelle hochgeladen
wurde. Wer nur auf die Art schaut, zeigt die alte README nicht an und eine neue
schon — und sucht den Unterschied an der falschen Stelle.
### ⬜ Als Nächstes
**Der Vergleichsmaßstab ist weg.** Am 11.08.2026 sind die Lite-Container