Add a pytest suite covering the pure logic of the DeDRM and Obok plugins.
Because the plugins normally run inside calibre and use intra-package
imports, tests/dedrm_test_utils.py sets up an import shim (plugin dirs on
sys.path, a minimal calibre stub, and a synthetic `dedrm` package) so the
modules can be imported and exercised standalone.
Coverage:
- alfcrypto: PC1 and Topaz cipher round-trips, PBKDF2 vs hashlib.
- kgenpids / kindlepid: PID encoding, bit-field extraction, device-PID
and serial-PID known vectors, CRC32.
- mobidedrm: PC1 round-trip, trailing-data sizing, bad-key handling.
- ineptpdf / ineptepub: nunpack and PKCS7 unpad.
- topazextract: encoded number/string parsing, plus a regression test
that a malicious header tag ("../../evil") cannot escape the output
directory (covers the path-traversal fix).
- obok: unpad, hash-key table, SafeUnbuffered str/bytes handling.
- utilities / argv_utils: uStrCmp normalisation, unicode_argv.
- erdr2pml: deXOR/sanitiseFileName, skipped on Python 3.13+ where the
module's `cgi` import is unavailable.
Run with `poetry install` then `poetry run pytest`. 36 pass, 2 skip on
Python 3.13+.
Note: this surfaced two dead-code modules that are broken on Python 3
(aescbc's pure-Python AES and kgenpids.decode, neither on a live path,
since the real crypto goes through pycryptodome); left as-is here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""Test helpers for importing the calibre plugin modules outside of calibre.
|
|
|
|
The DeDRM and Obok plugins normally run inside calibre's bundled Python and use
|
|
intra-package (``from .x import y``) imports. To unit-test the pure logic we:
|
|
|
|
* put the plugin source directories on ``sys.path`` (for absolute imports such
|
|
as ``import kgenpids`` / ``import aescbc``);
|
|
* provide a minimal ``calibre`` stub for the few modules that import it;
|
|
* register a synthetic ``dedrm`` package whose ``__path__`` points at the
|
|
DeDRM_plugin directory, so modules that use relative imports resolve without
|
|
executing the real (calibre-dependent) ``__init__.py``.
|
|
|
|
Modules with no relative imports (alfcrypto, kgenpids, argv_utils, aescbc, obok)
|
|
are loaded top-level; modules with ``from .`` imports are loaded as ``dedrm.X``.
|
|
"""
|
|
|
|
import importlib
|
|
import os
|
|
import sys
|
|
import types
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
DEDRM = os.path.join(ROOT, "DeDRM_plugin")
|
|
OBOK = os.path.join(ROOT, "Obok_plugin", "obok")
|
|
|
|
|
|
def _ensure_on_path(path):
|
|
if path not in sys.path:
|
|
sys.path.insert(0, path)
|
|
|
|
|
|
def _setup():
|
|
_ensure_on_path(DEDRM)
|
|
_ensure_on_path(OBOK)
|
|
|
|
# Minimal calibre stub (only the bits the plugin modules touch at import).
|
|
if "calibre" not in sys.modules:
|
|
sys.modules["calibre"] = types.ModuleType("calibre")
|
|
constants = types.ModuleType("calibre.constants")
|
|
constants.iswindows = sys.platform.startswith("win")
|
|
sys.modules["calibre.constants"] = constants
|
|
|
|
# Synthetic package so `from .x import y` works without the real __init__.py.
|
|
if "dedrm" not in sys.modules:
|
|
pkg = types.ModuleType("dedrm")
|
|
pkg.__path__ = [DEDRM]
|
|
pkg.__package__ = "dedrm"
|
|
sys.modules["dedrm"] = pkg
|
|
|
|
|
|
_setup()
|
|
|
|
|
|
def load(module_name, package=None):
|
|
"""Import and return a plugin module.
|
|
|
|
Pass ``package="dedrm"`` for modules that use relative imports.
|
|
"""
|
|
if package:
|
|
return importlib.import_module("{0}.{1}".format(package, module_name))
|
|
return importlib.import_module(module_name)
|