From 56d92c153b250270419443cc603a39eeafd6bfc5 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Jun 2026 09:37:15 +0200 Subject: [PATCH] mail: robustes Decoding gegen unbekannte Charsets (x-unknown); OAuth-Token 30 Tage - _safe_decode() faengt LookupError bei unbekannten/kaputten Mail-Charsets (z.B. 'x-unknown') ab, Fallback utf-8 -> latin-1. Verhindert Crash der Mail-Suche/Read bei einzelnen kaputt-kodierten Mails. - common.py: OAuth access_token Lifetime 24h -> 30 Tage (weniger Re-Auth in claude.ai) - .gitignore: *.before-* Backups ausschliessen Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 1 + common.py | 2 +- mail/server.py | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 5710f17..b73d7b9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__/ tokens.json config.json .active_tokens.json +*.before-* diff --git a/common.py b/common.py index 063bc66..5f5561f 100644 --- a/common.py +++ b/common.py @@ -193,7 +193,7 @@ async def oauth_token(request: Request): return JSONResponse({"error": "unsupported_grant_type"}, status_code=400) access_token = secrets.token_urlsafe(48) - expires_in = 86400 + expires_in = 2592000 # 30 days tokens = _load_access_tokens() # Cleanup expired now = time.time() diff --git a/mail/server.py b/mail/server.py index 4a7eee2..06c90d8 100644 --- a/mail/server.py +++ b/mail/server.py @@ -46,6 +46,21 @@ mcp = FastMCP("Mail", stateless_http=True, transport_security={"enable_dns_rebinding_protection": False}) +def _safe_decode(payload, charset): + """Decode bytes robust gegen unbekannte/kaputte Charsets (z.B. 'x-unknown').""" + if not isinstance(payload, bytes): + return str(payload) + for cs in (charset, "utf-8", "latin-1"): + if not cs: + continue + try: + return payload.decode(cs, errors="replace") + except (LookupError, TypeError): + continue + # Letzter Fallback: latin-1 akzeptiert jedes Byte + return payload.decode("latin-1", errors="replace") + + def _decode_hdr(raw): if not raw: return "" @@ -53,7 +68,7 @@ def _decode_hdr(raw): decoded = [] for data, charset in parts: if isinstance(data, bytes): - decoded.append(data.decode(charset or "utf-8", errors="replace")) + decoded.append(_safe_decode(data, charset)) else: decoded.append(str(data)) return " ".join(decoded) @@ -65,16 +80,16 @@ def _get_body(msg): if part.get_content_type() == "text/plain": payload = part.get_payload(decode=True) if payload: - return payload.decode(part.get_content_charset() or "utf-8", errors="replace") + return _safe_decode(payload, part.get_content_charset()) for part in msg.walk(): if part.get_content_type() == "text/html": payload = part.get_payload(decode=True) if payload: - return "[HTML] " + payload.decode(part.get_content_charset() or "utf-8", errors="replace") + return "[HTML] " + _safe_decode(payload, part.get_content_charset()) else: payload = msg.get_payload(decode=True) if payload: - return payload.decode(msg.get_content_charset() or "utf-8", errors="replace") + return _safe_decode(payload, msg.get_content_charset()) return ""