fix excel webp mimetype registration for openpyxl

This commit is contained in:
Mayorz 2026-03-04 16:45:10 +08:00
parent 629a520201
commit c382665753

View File

@ -7,6 +7,7 @@ import mimetypes
import re
import ssl
import tempfile
import zipfile
import urllib.error
import urllib.parse
import urllib.request
@ -16,6 +17,7 @@ from typing import Iterable
from openpyxl import load_workbook
from openpyxl.drawing.image import Image as XLImage
from openpyxl.packaging import manifest as openpyxl_manifest
from openpyxl.utils import column_index_from_string, get_column_letter
try:
from PIL import Image as PILImage
@ -44,8 +46,98 @@ IMAGE_HINT_HEADERS = (
"image url",
"图片",
)
mimetypes.add_type("image/webp", ".webp", strict=True)
mimetypes.add_type("image/webp", ".webp", strict=False)
def _iter_mimetype_modules():
yield mimetypes
compat = getattr(openpyxl_manifest, "mimetypes", None)
if compat is not None:
yield compat
def _resolve_type_maps(module) -> tuple[dict, dict]:
type_maps = getattr(module, "types_map", {})
if isinstance(type_maps, tuple) and len(type_maps) >= 2:
loose_map, strict_map = type_maps[0], type_maps[1]
if isinstance(strict_map, dict) and isinstance(loose_map, dict):
return strict_map, loose_map
if (
isinstance(type_maps, dict)
and isinstance(type_maps.get(True), dict)
and isinstance(type_maps.get(False), dict)
):
return type_maps[True], type_maps[False]
if isinstance(type_maps, dict):
loose_map = getattr(module, "common_types", {})
if not isinstance(loose_map, dict):
loose_map = type_maps
return type_maps, loose_map
return {}, {}
def _register_type(module, extension: str, mime_type: str) -> None:
add_type = getattr(module, "add_type", None)
if callable(add_type):
try:
add_type(mime_type, extension, strict=True)
except TypeError:
try:
add_type(mime_type, extension)
except Exception:
pass
except Exception:
pass
try:
add_type(mime_type, extension, strict=False)
except Exception:
pass
strict_map, loose_map = _resolve_type_maps(module)
strict_map[extension] = mime_type
loose_map.setdefault(extension, mime_type)
def ensure_mimetype_for_ext(ext: str) -> None:
normalized_raw = str(ext or "").strip()
if not normalized_raw:
return
if not normalized_raw.startswith("."):
normalized_raw = f".{normalized_raw}"
normalized = normalized_raw.lower()
guessed = mimetypes.types_map.get(normalized)
if not guessed:
guessed = mimetypes.common_types.get(normalized)
if not guessed:
guessed, _ = mimetypes.guess_type(f"x{normalized}", strict=False)
if not guessed and normalized in {".webp", ".WEBP"}:
guessed = "image/webp"
if not guessed:
guessed = "application/octet-stream"
variants = {normalized}
variants.add(normalized_raw)
if normalized_raw != normalized:
variants.add(normalized_raw.upper())
for module in _iter_mimetype_modules():
for variant in variants:
_register_type(module, variant, guessed)
ensure_mimetype_for_ext(".webp")
def register_mimetypes_from_archive(path: Path) -> None:
try:
with zipfile.ZipFile(path, "r") as archive:
for name in archive.namelist():
suffix = Path(name).suffix
if suffix:
ensure_mimetype_for_ext(suffix)
except Exception:
return
@dataclass
@ -247,6 +339,7 @@ def download_image(url: str, timeout: int, ssl_context):
def to_excel_safe_image(payload: bytes, extension: str) -> tuple[bytes, str]:
ext = (extension or "").lower()
ensure_mimetype_for_ext(ext)
if ext in EXCEL_SAFE_EXTENSIONS and ext not in UNSAFE_OR_UNKNOWN_EXTENSIONS:
return payload, ext
@ -367,6 +460,7 @@ def main() -> int:
print(f"ERROR: input does not exist: {input_path}")
return 1
register_mimetypes_from_archive(input_path)
ssl_context = build_ssl_context(args.skip_tls_verify)
workbook = load_workbook(input_path)