diff --git a/backend/app/bilder.py b/backend/app/bilder.py index ae5b8ab..220717f 100644 --- a/backend/app/bilder.py +++ b/backend/app/bilder.py @@ -23,7 +23,7 @@ def hat_metadaten(data: bytes) -> bool: 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")) \ or bool(getattr(img, "text", None)) - except (OSError, ValueError, Image.DecompressionBombError): + except Exception: # kaputte oder exotische Datei: als "ohne Metadaten" behandeln return False @@ -50,5 +50,5 @@ def ohne_metadaten(data: bytes, ext: str) -> bytes: else: img.save(out, "WEBP", quality=90, icc_profile=icc) 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ß.") diff --git a/backend/app/migrations.py b/backend/app/migrations.py index fca704c..e0b4b1b 100644 --- a/backend/app/migrations.py +++ b/backend/app/migrations.py @@ -205,18 +205,19 @@ def _v2_datenschutz(conn) -> None: cleaned = sanitize(row["content"] or "") if cleaned != (row["content"] or ""): 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" if uploads.is_dir(): for f in uploads.iterdir(): if not re.fullmatch(r"[a-f0-9]{24}\.(jpg|png|webp)", f.name): continue - data = f.read_bytes() - if hat_metadaten(data): - try: + try: + data = f.read_bytes() + if hat_metadaten(data): f.write_bytes(ohne_metadaten(data, f.suffix[1:])) - except ValidationError: - log.warning("Bild %s konnte nicht bereinigt werden", f.name) + except (OSError, ValidationError) as e: + log.warning("Bild %s konnte nicht bereinigt werden: %s", f.name, e) # Reihenfolge = Versionsnummer (Index + 1). Nur anhängen, nie umsortieren oder entfernen. diff --git a/backend/tests/test_datenschutz.py b/backend/tests/test_datenschutz.py index f2347cb..1391a45 100644 --- a/backend/tests/test_datenschutz.py +++ b/backend/tests/test_datenschutz.py @@ -143,3 +143,19 @@ def test_umstellung_bestehender_daten(conn, login): assert rows == {(99, "geheim"), (home, "alt")} assert "tracker" not in conn.execute("SELECT content FROM projects WHERE id = 'a1'").fetchone()[0] 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)