Icon-Fetch: echten Bildtyp per Magic Bytes erkennen statt Server-Header zu vertrauen
Live gefunden beim Icon-Backfill (Phase 6): learningequality.org liefert sein Favicon mit Content-Type "image/svg+xml", tatsächlich sind die Bytes ein PNG-in-ICO-Container - wäre im Browser als kaputtes Bild gelandet (SVG-Parser auf Binärdaten). _erkanntes_bildformat() sniffed jetzt die echten Magic Bytes (PNG/JPEG/GIF/ICO/WEBP/SVG-Erkennung), der HTTP-Header dient nur noch als Fallback. Zentral in _fetch_url_bytes() gefixt, damit sowohl Webapp- als auch Merkmal-Icons profitieren (gleicher Fetch-Pfad). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
This commit is contained in:
1 parent
95946bd11c
commit
a6fecdd818
1 file changed
+30
-1
@@ -769,6 +769,35 @@ FAVICON_LINK_REGEX = re.compile(
|
||||
FAVICON_HREF_REGEX = re.compile(r'href=["\']([^"\']+)["\']', re.IGNORECASE)
|
||||
|
||||
|
||||
# Live gefunden (11.09.2026, Phase 6 Icon-Backfill): learningequality.org
|
||||
# liefert sein Favicon mit Content-Type "image/svg+xml", tatsaechlich sind
|
||||
# die Bytes ein PNG-in-ICO-Container - ein Browser haette das als kaputtes
|
||||
# Bild angezeigt (SVG-Parser auf Binaerdaten). Server-Header sind nicht
|
||||
# vertrauenswuerdig genug, um sie ungeprueft in icon_content_type zu
|
||||
# uebernehmen - _erkanntes_bildformat() sniffed stattdessen die echten
|
||||
# Magic Bytes, der Header dient nur noch als Fallback, falls die Signatur
|
||||
# nichts Bekanntes ergibt (z.B. ein Format ausserhalb dieser Liste).
|
||||
_BILD_SIGNATUREN = [
|
||||
(b"\x89PNG\r\n\x1a\n", "image/png"),
|
||||
(b"\xff\xd8\xff", "image/jpeg"),
|
||||
(b"GIF87a", "image/gif"),
|
||||
(b"GIF89a", "image/gif"),
|
||||
(b"\x00\x00\x01\x00", "image/x-icon"),
|
||||
]
|
||||
|
||||
|
||||
def _erkanntes_bildformat(data: bytes) -> str | None:
|
||||
kopf = data[:16]
|
||||
for signatur, mime in _BILD_SIGNATUREN:
|
||||
if kopf.startswith(signatur):
|
||||
return mime
|
||||
if kopf.startswith(b"RIFF") and data[8:12] == b"WEBP":
|
||||
return "image/webp"
|
||||
if b"<svg" in data[:512]:
|
||||
return "image/svg+xml"
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_url_bytes(url: str, max_bytes: int) -> tuple[bytes, str] | None:
|
||||
"""Best-effort GET mit Groessen-/Zeitlimit. None bei jedem Fehlschlag -
|
||||
Aufrufer entscheiden ueber Fallback, kein Exception-Handling noetig."""
|
||||
@@ -782,7 +811,7 @@ def _fetch_url_bytes(url: str, max_bytes: int) -> tuple[bytes, str] | None:
|
||||
data = response.read(max_bytes + 1)
|
||||
if len(data) > max_bytes:
|
||||
return None
|
||||
return data, content_type
|
||||
return data, _erkanntes_bildformat(data) or content_type
|
||||
except (urllib.error.URLError, TimeoutError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
Reference in new issue
Block a user