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