"""Fahrzeugbilder direkt aus der Oberfläche hochladen und löschen (Einstellungen -> Fahrzeugbilder). Nur die festen, im Frontend fest verdrahteten Dateinamen sind erlaubt - eine Positivliste, damit ein manipulierter Dateiname aus dem Frontend niemals außerhalb von www/bilder/ schreiben kann (kein Pfad-Traversal über "../" oder absolute Pfade). Die Bytes werden unverändert unter dem erwarteten Namen abgelegt, unabhängig vom tatsächlichen Bildformat des Originalfotos (z. B. JPEG von einem iPhone unter einem ".webp"-Namen) - Browser stellen nach den tatsächlichen Bilddaten dar, nicht nach der Dateiendung. Eine Größenbeschränkung gibt es bewusst nicht: jede Fotogröße muss hochladbar sein. WARUM DIESE DATEIEN NICHT IM INTEGRATIONSORDNER LIEGEN: sie sind Nutzerdaten, kein Auslieferbestandteil. Lägen sie unter custom_components/audi_dashboard/, würde der nächste install.ps1-Lauf sie mitlöschen (er leert den Zielordner vor dem Kopieren, siehe install.ps1). /config/www/bilder/ gehört dagegen dem Nutzer und wird von keinem Update angefasst; ausgeliefert wird es weiterhin unter /local/bilder/. """ from __future__ import annotations import base64 import binascii import logging import os from homeassistant.core import HomeAssistant _LOGGER = logging.getLogger(__name__) ERLAUBTE_DATEINAMEN = frozenset({ "seitenansicht.webp", "seitenansicht-winter.webp", "front-schraeg.webp", "frontansicht.webp", "heckansicht.webp", "cockpit.webp", "scheinwerfer.webp", "sitze.webp", "rad-sommer.webp", "rad-winter.webp", "draufsicht.webp", }) def _pfad(ordner: str, dateiname: str | None) -> str | None: if not dateiname or dateiname not in ERLAUBTE_DATEINAMEN: return None return os.path.join(ordner, dateiname) def _schreiben(pfad: str, rohdaten: bytes) -> None: os.makedirs(os.path.dirname(pfad), exist_ok=True) tmp = pfad + ".tmp" with open(tmp, "wb") as datei: datei.write(rohdaten) os.replace(tmp, pfad) def _loeschen(pfad: str) -> bool: if not os.path.exists(pfad): return False os.remove(pfad) return True async def hochladen( hass: HomeAssistant, ordner: str, dateiname: str | None, daten_base64: str | None ) -> None: pfad = _pfad(ordner, dateiname) if not pfad or not daten_base64: _LOGGER.warning("Bild-Upload abgelehnt (dateiname=%s)", dateiname) return try: rohdaten = base64.b64decode(daten_base64) except (binascii.Error, ValueError) as fehler: _LOGGER.error("Bild-Upload: Daten nicht lesbar (%s)", fehler) return await hass.async_add_executor_job(_schreiben, pfad, rohdaten) _LOGGER.info("Fahrzeugbild gespeichert (%s, %s Bytes)", dateiname, len(rohdaten)) async def loeschen(hass: HomeAssistant, ordner: str, dateiname: str | None) -> None: pfad = _pfad(ordner, dateiname) if not pfad: _LOGGER.warning("Bild-Löschen abgelehnt (dateiname=%s)", dateiname) return if await hass.async_add_executor_job(_loeschen, pfad): _LOGGER.info("Fahrzeugbild gelöscht (%s)", dateiname)