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
This commit is contained in:
nightq
2026-04-15 08:39:48 +00:00
committed by Teknium
parent 2b685f08fa
commit f39680de45
2 changed files with 18 additions and 0 deletions
+4
View File
@@ -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
+14
View File
@@ -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