Skip to content

Commit fd646ee

Browse files
authored
BUG: Check version of crypt provider (#2115)
Closes #2107
1 parent 9cb22c4 commit fd646ee

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

pypdf/_crypt_providers/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
rc4_decrypt,
4040
rc4_encrypt,
4141
)
42+
from pypdf._utils import Version
43+
44+
if Version(crypt_provider[1]) <= Version("3.0"):
45+
# This is due to the backend parameter being required back then:
46+
# https://cryptography.io/en/latest/changelog/#v3-1
47+
raise ImportError("cryptography<=3.0 is not supported") # pragma: no cover
4248
except ImportError:
4349
try:
4450
from pypdf._crypt_providers._pycryptodome import ( # type: ignore

pypdf/_utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import functools
3333
import logging
34+
import re
3435
import warnings
3536
from dataclasses import dataclass
3637
from datetime import datetime, timezone
@@ -41,6 +42,7 @@
4142
Any,
4243
Callable,
4344
Dict,
45+
List,
4446
Optional,
4547
Pattern,
4648
Tuple,
@@ -588,3 +590,52 @@ def replace(self, new_image: Any, **kwargs: Any) -> None:
588590
self.name = self.name[: self.name.rfind(".")] + extension
589591
self.data = byte_stream
590592
self.image = img
593+
594+
595+
@functools.total_ordering
596+
class Version:
597+
COMPONENT_PATTERN = re.compile(r"^(\d+)(.*)$")
598+
599+
def __init__(self, version_str: str) -> None:
600+
self.version_str = version_str
601+
self.components = self._parse_version(version_str)
602+
603+
def _parse_version(self, version_str: str) -> List[Tuple[int, str]]:
604+
components = version_str.split(".")
605+
parsed_components = []
606+
for component in components:
607+
match = Version.COMPONENT_PATTERN.match(component)
608+
if not match:
609+
parsed_components.append((0, component))
610+
continue
611+
integer_prefix = match.group(1)
612+
suffix = match.group(2)
613+
if integer_prefix is None:
614+
integer_prefix = 0
615+
parsed_components.append((int(integer_prefix), suffix))
616+
return parsed_components
617+
618+
def __eq__(self, other: object) -> bool:
619+
if not isinstance(other, Version):
620+
return False
621+
return self.components == other.components
622+
623+
def __lt__(self, other: Any) -> bool:
624+
if not isinstance(other, Version):
625+
raise ValueError(f"Version cannot be compared against {type(other)}")
626+
min_len = min(len(self.components), len(other.components))
627+
for i in range(min_len):
628+
self_value, self_suffix = self.components[i]
629+
other_value, other_suffix = other.components[i]
630+
631+
if self_value < other_value:
632+
return True
633+
elif self_value > other_value:
634+
return False
635+
636+
if self_suffix < other_suffix:
637+
return True
638+
elif self_suffix > other_suffix:
639+
return False
640+
641+
return len(self.components) < len(other.components)

tests/test_utils.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pypdf._utils
88
from pypdf._utils import (
99
File,
10+
Version,
1011
_get_max_pdf_version_header,
1112
_human_readable_bytes,
1213
deprecate_with_replacement,
@@ -351,3 +352,51 @@ def test_parse_datetime_err():
351352
parse_iso8824_date("D:20210408T054711Z")
352353
assert ex.value.args[0] == "Can not convert date: D:20210408T054711Z"
353354
assert parse_iso8824_date("D:20210408054711").tzinfo is None
355+
356+
357+
@pytest.mark.parametrize(
358+
("left", "right", "is_less_than"),
359+
[
360+
("1", "2", True),
361+
("2", "1", False),
362+
("1", "1", False),
363+
("1.0", "1.1", True),
364+
("1", "1.1", True),
365+
# suffix left
366+
("1a", "2", True),
367+
("2a", "1", False),
368+
("1a", "1", False),
369+
("1.0a", "1.1", True),
370+
# I'm not sure about that, but seems special enoguht that it
371+
# probably doesn't matter:
372+
("1a", "1.1", False),
373+
# suffix right
374+
("1", "2a", True),
375+
("2", "1a", False),
376+
("1", "1a", True),
377+
("1.0", "1.1a", True),
378+
("1", "1.1a", True),
379+
("", "0.0.0", True),
380+
# just suffix matters ... hm, I think this is actually wrong:
381+
("1.0a", "1.0", False),
382+
("1.0", "1.0a", True),
383+
],
384+
)
385+
def test_version_compare(left, right, is_less_than):
386+
assert (Version(left) < Version(right)) is is_less_than
387+
388+
389+
def test_version_compare_equal_str():
390+
a = Version("1.0")
391+
assert (a == "1.0") is False
392+
393+
394+
def test_version_compare_lt_str():
395+
a = Version("1.0")
396+
with pytest.raises(ValueError) as exc:
397+
a < "1.0" # noqa
398+
assert exc.value.args[0] == "Version cannot be compared against <class 'str'>"
399+
400+
401+
def test_bad_version():
402+
assert Version("a").components == [(0, "a")]

0 commit comments

Comments
 (0)