feat(drawing): Vorschaubilder und ein Zeichenbrett, das zur Oberfläche passt
Vorschaubilder entstehen sonst serverseitig aus den hochgeladenen Bytes – bei
einer Zeichnung steht dort JSON, daraus wird ohne Browser kein Bild. Wer sie
gerade bearbeitet, hat aber genau den Browser, der sie ohnehin zeichnet.
Neu: POST /api/v1/files/{id}/thumbnail. Der Editor schickt nach dem Speichern
ein PNG hinterher.
Den Bytes wird dabei nicht geglaubt: Sie werden nicht abgelegt, sondern durch
denselben Weg geschickt wie jedes andere Vorschaubild – dekodieren,
verkleinern, neu kodieren. Danach ist es mit Sicherheit ein Bild und hat die
übliche Grösse; nichts anderes kann im thumbs-Bereich landen. Dazu Schreibrecht
auf die Datei: Wer sie nicht ändern darf, bestimmt auch nicht, wie sie in der
Liste aussieht.
Das Hochladen läuft nach dem Speichern und ohne darauf zu warten. Ein
fehlgeschlagenes Vorschaubild ist ein Schönheitsfehler, kein Grund, das
Speichern scheitern zu lassen – und bleibt deshalb stumm.
Politur am Zeichenbrett: dunkles Thema (ein weisses Brett in dunkler Oberfläche
war der auffälligste Bruch), „Unsaved" als Marke statt eines Punktes hinter dem
Namen, Strg+S, Dateisymbol und Name ohne Endung.
„Open" und „Save to…" sind aus. Beide gehen am Browser vorbei: „Open" ersetzte
den Inhalt dieser Zeichnung durch eine fremde Datei, „Save to…" schriebe eine
zweite Fassung in den Download-Ordner, die niemand wiederfindet.
Selbsttest 12 → 18 Prüfungen, darunter die beiden Ablehnungen (kein Bild → 400,
fremder Typ → 415).
Geprüft: go build/vet sauber, alle Go-Pakete grün, 261 Prüfungen der acht
Selbsttest-Suiten grün, 53 Vitest-Tests, tsc sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e1eaf9cbf
commit
dd68bc594d
6 files changed
+277
-3
No files matched your search
@@ -15,6 +15,8 @@ ueberschreiben.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import zlib
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
@@ -91,6 +93,26 @@ def zeichnung(elemente=0):
|
||||
}).encode()
|
||||
|
||||
|
||||
def winziges_png() -> bytes:
|
||||
"""Ein gueltiges 1x1-PNG, von Hand gebaut – ohne Abhaengigkeit."""
|
||||
def chunk(typ, daten):
|
||||
c = typ + daten
|
||||
return struct.pack(">I", len(daten)) + c + struct.pack(">I", zlib.crc32(c))
|
||||
ihdr = struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0)
|
||||
return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr)
|
||||
+ chunk(b"IDAT", zlib.compress(b"\x00\xff\x00\x00")) + chunk(b"IEND", b""))
|
||||
|
||||
|
||||
def roh_senden(opener, path, daten: bytes, typ: str, csrf):
|
||||
req = urllib.request.Request(BASE + path, data=daten, method="POST",
|
||||
headers={"Content-Type": typ, **csrf})
|
||||
try:
|
||||
with opener.open(req) as r:
|
||||
return r.status
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code
|
||||
|
||||
|
||||
print(f"Zeichnungs-Selbsttest gegen {BASE}\n")
|
||||
admin, CSRF = anmelden(USER, PASSWORT)
|
||||
_, ich = ruf(admin, "GET", "/api/v1/auth/me")
|
||||
@@ -167,6 +189,28 @@ try:
|
||||
angelegt.append(gz["id"])
|
||||
check("Zeichnung im Gruppenordner", gz["folder_id"] == gordner["id"])
|
||||
|
||||
print("\n── Vorschaubild ──")
|
||||
# Der Server kann es nicht selbst rechnen: In der Datei steht JSON. Der
|
||||
# Browser, der gerade zeichnet, kann es – deshalb darf er es schicken.
|
||||
check("frische Zeichnung hat noch keines", not datei.get("has_thumbnail"))
|
||||
st = roh_senden(admin, f"/api/v1/files/{datei['id']}/thumbnail", winziges_png(), "image/png", CSRF)
|
||||
check("Vorschaubild setzen (204)", st == 204, st)
|
||||
_, liste2 = ruf(admin, "GET", "/api/v1/files")
|
||||
jetzt = next((f for f in (liste2 or {}).get("files", []) if f["id"] == datei["id"]), None)
|
||||
check("und die Liste weiss davon", jetzt and jetzt.get("has_thumbnail"), jetzt)
|
||||
st, _ = ruf(admin, "GET", f"/api/v1/files/{datei['id']}/thumbnail", None, None, True)
|
||||
check("es laesst sich abrufen", st == 200, st)
|
||||
|
||||
# **Den Bytes wird nicht geglaubt.** Sie gehen durch denselben Weg wie
|
||||
# jedes andere Vorschaubild – dekodieren, verkleinern, neu kodieren.
|
||||
# Sonst liesse sich Beliebiges in den thumbs-Bereich legen.
|
||||
st = roh_senden(admin, f"/api/v1/files/{datei['id']}/thumbnail", b"<html>kein bild</html>",
|
||||
"image/png", CSRF)
|
||||
check("was kein Bild ist, wird abgelehnt (400)", st == 400, st)
|
||||
st = roh_senden(admin, f"/api/v1/files/{datei['id']}/thumbnail", winziges_png(),
|
||||
"text/html", CSRF)
|
||||
check("fremder Typ wird abgelehnt (415)", st == 415, st)
|
||||
|
||||
print("\n── Schriften werden selbst ausgeliefert ──")
|
||||
# Ohne das laedt Excalidraw sie von aussen – und die Sicherheitsrichtlinie
|
||||
# verbietet genau das. Man saehe eine Ersatzschrift und suchte den Grund
|
||||
|
||||
Reference in new issue
Block a user