Source code for univention.lib.umc_module

#!/usr/bin/python3
# SPDX-FileCopyrightText: 2013-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

"""
Univention common Python library with
helper functions for MIME type handling.
"""

from __future__ import annotations

import bz2
import io
import zlib
from typing import TYPE_CHECKING, Any

import magic
from PIL import Image


if TYPE_CHECKING:
    from collections.abc import Callable


MIME_TYPE = magic.Magic(magic.MAGIC_MIME_TYPE)
MIME_DESCRIPTION = magic.Magic(magic.MAGIC_NONE)

UMC_ICON_BASEDIR = "/usr/share/univention-management-console-frontend/js/dijit/themes/umc/icons"

compression_mime_type_handlers = {
    "application/x-gzip": lambda x: zlib.decompress(x, 16 + zlib.MAX_WBITS),
    # "application/gzip": lambda x: zlib.decompress(x, 16 + zlib.MAX_WBITS),  # TODO: enable Bug #51594
    "application/x-bzip2": bz2.decompress,
    "application/bzip2": bz2.decompress,
}


[docs] def get_mime_type(data: bytes) -> str: """ Guess |MIME| type of data. :param bytes data: Some data. :returns: The |MIME| type string. :rtype: str """ return MIME_TYPE.from_buffer(data)
[docs] def get_mime_description(data: bytes) -> str: """ Guess type of data silimar to :command:`file`. :param bytes data: Some data. :returns: A descriptive string. :rtype: str """ return MIME_DESCRIPTION.from_buffer(data)
[docs] def compression_mime_type_of_buffer(data: bytes) -> tuple[str, Callable[[Any], bytes]]: """ Guess |MIME| type of compressed data. :param bytes data: Some compressed data. :returns: A 2-tuple (mime_type, uncompress_function). :rtype: tuple[str, str] :raises univention.admin.uexceptions.valueError: if the compression format is not recognized. """ mime_type = get_mime_type(data) if mime_type in compression_mime_type_handlers: return (mime_type, compression_mime_type_handlers[mime_type]) else: import univention.admin.uexceptions raise univention.admin.uexceptions.valueError("Not a supported compression format: %s" % (mime_type,))
[docs] def uncompress_buffer(data: bytes) -> tuple[str | None, bytes]: """ Return uncompressed data and its |MIME| type. :param bytes data: Some compressed data. :returns: A 2-tuple (mime_type, uncompressed_data). On errors `mime_type` is `None` and `uncompressed_data` is `data`. :rtype: tuple[str, bytes] """ import univention.admin.uexceptions try: (mime_type, compression_mime_type_handler) = compression_mime_type_of_buffer(data) return (mime_type, compression_mime_type_handler(data)) except univention.admin.uexceptions.valueError: return (None, data)
[docs] def uncompress_file(filename: str) -> tuple[str | None, bytes]: """ Return uncompressed file content and its |MIME| type. :param str filename: The name of the file. :returns: A 2-tuple (mime_type, uncompressed_data). On errors `mime_type` is `None` and `uncompressed_data` is `data`. :rtype: tuple[str, bytes] """ with open(filename, 'rb') as f: return uncompress_buffer(f.read())
[docs] def image_mime_type_of_buffer(data: bytes) -> str: """ Guess |MIME| type of image. :param bytes data: Some image data. :returns: The |MIME| type string. :rtype: str :raises univention.admin.uexceptions.valueError: if the image format is not supported. """ mime_type = get_mime_type(data) if mime_type in ('image/jpeg', 'image/png', 'image/svg+xml', 'application/x-gzip'): return mime_type else: import univention.admin.uexceptions raise univention.admin.uexceptions.valueError("Not a supported image format: %s" % (mime_type,))
[docs] def imagedimensions_of_buffer(data: bytes) -> tuple[int, int]: """ Return image dimension of image. :param bytes data: Some image data. :returns: A 2-tuple (width, height) :rtype: tuple[int, int] """ fp = io.BytesIO(data) im = Image.open(fp) return im.size
[docs] def imagecategory_of_buffer(data: bytes) -> tuple[str, str | None, str] | None: """ Return |MIME| types and size information for image. :strparam bytes data: Some (compressed) image data. :returns: a 3-tuple (image_mime_type, compression_mime_type, dimension) where `dimension` is `{width}x{height}` or `scalable`. `None` if the format is not recognized. :rtype: tuple[str, str, str] """ (compression_mime_type, uncompressed_data) = uncompress_buffer(data) mime_type = image_mime_type_of_buffer(uncompressed_data) if mime_type in ('image/jpeg', 'image/png'): return (mime_type, compression_mime_type, "%sx%s" % imagedimensions_of_buffer(uncompressed_data)) elif mime_type in ('image/svg+xml', 'application/x-gzip'): return (mime_type, compression_mime_type, "scalable") return None
[docs] def default_filename_suffix_for_mime_type(mime_type: str, compression_mime_type: str) -> str | None: """ Return default file name suffix for image. :param str mime_type: The |MIME| type of the image. :param str compression_mime_type: The |MIME| type of the compression. :returns: A suffix string or `None` if the image format is not supported. :rytpe: str """ if mime_type == 'image/svg+xml': if not compression_mime_type: return '.svg' elif compression_mime_type == 'application/x-gzip': return '.svgz' elif mime_type == 'image/png': return '.png' elif mime_type == 'image/jpeg': return '.jpg' return None