Start robuster: Problembilder verhindern die Umstellung nicht
- Migration Schritt 2: Lese-/Schreibfehler (z. B. Dateirechte) und kaputte Bilder werden protokolliert und übersprungen, statt den Serverstart abzubrechen - bilder.py: alle Pillow-Fehler werden zu einer Eingabe-Fehlermeldung bzw. "keine Metadaten" statt zu Fehler 500 - Test mit schreibgeschütztem und kaputtem Bild Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb20135876
commit
a1a1998d1e
3 files changed
+23
-6
No files matched your search
@@ -23,7 +23,7 @@ def hat_metadaten(data: bytes) -> bool:
|
|||||||
with Image.open(io.BytesIO(data)) as img:
|
with Image.open(io.BytesIO(data)) as img:
|
||||||
return bool(img.getexif()) or any(k in img.info for k in ("xmp", "XML:com.adobe.xmp", "comment", "exif")) \
|
return bool(img.getexif()) or any(k in img.info for k in ("xmp", "XML:com.adobe.xmp", "comment", "exif")) \
|
||||||
or bool(getattr(img, "text", None))
|
or bool(getattr(img, "text", None))
|
||||||
except (OSError, ValueError, Image.DecompressionBombError):
|
except Exception: # kaputte oder exotische Datei: als "ohne Metadaten" behandeln
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -50,5 +50,5 @@ def ohne_metadaten(data: bytes, ext: str) -> bytes:
|
|||||||
else:
|
else:
|
||||||
img.save(out, "WEBP", quality=90, icc_profile=icc)
|
img.save(out, "WEBP", quality=90, icc_profile=icc)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
except (OSError, ValueError, Image.DecompressionBombError):
|
except Exception: # Pillow wirft je nach Format verschiedene Fehler
|
||||||
raise ValidationError("Das Bild konnte nicht gelesen werden oder ist zu groß.")
|
raise ValidationError("Das Bild konnte nicht gelesen werden oder ist zu groß.")
|
||||||
@@ -205,18 +205,19 @@ def _v2_datenschutz(conn) -> None:
|
|||||||
cleaned = sanitize(row["content"] or "")
|
cleaned = sanitize(row["content"] or "")
|
||||||
if cleaned != (row["content"] or ""):
|
if cleaned != (row["content"] or ""):
|
||||||
conn.execute("UPDATE projects SET content = ? WHERE id = ?", (cleaned, row["id"]))
|
conn.execute("UPDATE projects SET content = ? WHERE id = ?", (cleaned, row["id"]))
|
||||||
# Hochgeladene Bilder: nur solche mit Metadaten neu schreiben (die anderen bleiben unverändert)
|
# Hochgeladene Bilder: nur solche mit Metadaten neu schreiben (die anderen bleiben unverändert).
|
||||||
|
# Ein einzelnes Problembild (Rechte, kaputte Datei) darf den Serverstart nie verhindern.
|
||||||
uploads = DATA_DIR / "uploads"
|
uploads = DATA_DIR / "uploads"
|
||||||
if uploads.is_dir():
|
if uploads.is_dir():
|
||||||
for f in uploads.iterdir():
|
for f in uploads.iterdir():
|
||||||
if not re.fullmatch(r"[a-f0-9]{24}\.(jpg|png|webp)", f.name):
|
if not re.fullmatch(r"[a-f0-9]{24}\.(jpg|png|webp)", f.name):
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
data = f.read_bytes()
|
data = f.read_bytes()
|
||||||
if hat_metadaten(data):
|
if hat_metadaten(data):
|
||||||
try:
|
|
||||||
f.write_bytes(ohne_metadaten(data, f.suffix[1:]))
|
f.write_bytes(ohne_metadaten(data, f.suffix[1:]))
|
||||||
except ValidationError:
|
except (OSError, ValidationError) as e:
|
||||||
log.warning("Bild %s konnte nicht bereinigt werden", f.name)
|
log.warning("Bild %s konnte nicht bereinigt werden: %s", f.name, e)
|
||||||
|
|
||||||
|
|
||||||
# Reihenfolge = Versionsnummer (Index + 1). Nur anhängen, nie umsortieren oder entfernen.
|
# Reihenfolge = Versionsnummer (Index + 1). Nur anhängen, nie umsortieren oder entfernen.
|
||||||
|
|||||||
@@ -143,3 +143,19 @@ def test_umstellung_bestehender_daten(conn, login):
|
|||||||
assert rows == {(99, "geheim"), (home, "alt")}
|
assert rows == {(99, "geheim"), (home, "alt")}
|
||||||
assert "tracker" not in conn.execute("SELECT content FROM projects WHERE id = 'a1'").fetchone()[0]
|
assert "tracker" not in conn.execute("SELECT content FROM projects WHERE id = 'a1'").fetchone()[0]
|
||||||
assert not Image.open(foto).getexif()
|
assert not Image.open(foto).getexif()
|
||||||
|
|
||||||
|
|
||||||
|
def test_problembilder_verhindern_start_nicht(conn):
|
||||||
|
"""Schreibgeschütztes Foto mit GPS und kaputte Datei: Umstellung läuft trotzdem durch."""
|
||||||
|
uploads = DATA_DIR / "uploads"
|
||||||
|
gesperrt = uploads / ("cd" * 12 + ".jpg")
|
||||||
|
gesperrt.write_bytes(jpeg_mit_gps())
|
||||||
|
gesperrt.chmod(0o444)
|
||||||
|
(uploads / ("ef" * 12 + ".png")).write_bytes(b"\x89PNG kaputt")
|
||||||
|
try:
|
||||||
|
with conn:
|
||||||
|
conn.execute("PRAGMA user_version = 1")
|
||||||
|
db.init()
|
||||||
|
assert conn.execute("PRAGMA user_version").fetchone()[0] == 2
|
||||||
|
finally:
|
||||||
|
gesperrt.chmod(0o644)
|
||||||
Reference in new issue
Block a user