From f39680de45abcba0c9d8d787574f4ecc95b845d3 Mon Sep 17 00:00:00 2001 From: nightq Date: Wed, 15 Apr 2026 08:39:48 +0000 Subject: [PATCH] fix(wecom): inbound images cached with their real extension, not .bin WeCom's CDN returns `content-type: application/octet-stream` for images. `mimetypes.guess_extension` maps that to ".bin", a truthy value that short-circuited `_guess_extension()` before the magic-byte detector ran, so every inbound image was cached as `img_*.bin`. Treat ".bin" as "unknown" so the URL suffix / magic-byte fallback decides. The second half of the report (URL-encoded, unpadded `aeskey`) is already handled on main by `_decrypt_file_bytes`. Salvage of PR #10187 by @nightq onto `plugins/platforms/wecom/media.py`. Fixes #10085 --- plugins/platforms/wecom/media.py | 4 ++++ tests/gateway/test_wecom.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/plugins/platforms/wecom/media.py b/plugins/platforms/wecom/media.py index 319024559a..96a2be68b3 100644 --- a/plugins/platforms/wecom/media.py +++ b/plugins/platforms/wecom/media.py @@ -132,7 +132,11 @@ class WeComMediaMixin: @staticmethod def _guess_extension(url: str, content_type: str, fallback: str) -> str: + # WeCom's CDN labels images application/octet-stream; mimetypes maps that to ".bin", + # which is truthy and used to win over the magic-byte fallback (#10085). ext = mimetypes.guess_extension(content_type) if content_type else None + if ext == ".bin": + ext = None return ext or Path(urlparse(url).path).suffix or fallback @staticmethod diff --git a/tests/gateway/test_wecom.py b/tests/gateway/test_wecom.py index fe62c65a5e..e8b0054266 100644 --- a/tests/gateway/test_wecom.py +++ b/tests/gateway/test_wecom.py @@ -30,6 +30,20 @@ class TestWeComAdapterInit: assert WeComAdapter.SUPPORTS_MESSAGE_EDITING is False +class TestWeComInboundImageExtension: + def test_octet_stream_falls_through_to_magic_bytes(self): + """WeCom's CDN serves images as application/octet-stream; the cached file must get the + real image extension from magic bytes, not ".bin" (#10085).""" + from plugins.platforms.wecom.adapter import WeComAdapter + + jpeg = b"\xff\xd8\xff\xe0" + b"\x00" * 16 + ext = WeComAdapter._guess_extension( + "https://wwcdn.weixin.qq.com/img?aeskey=abc", "application/octet-stream", + fallback=WeComAdapter._detect_image_ext(jpeg)) + assert ext == ".jpg" + assert WeComAdapter._guess_extension("https://x/y.png", "image/png", fallback=".jpg") == ".png" + + class TestWeComAdapterAuthzScope: """dm_policy/allowlist reads must honor the profile secret scope under multiplexing (#93522): a secondary profile's own scope is authoritative