Skip to content

Commit

Permalink
Merge branch 'main' into iss2912
Browse files Browse the repository at this point in the history
  • Loading branch information
pubpub-zz authored Oct 23, 2024
2 parents 2f98e6c + 59ae169 commit 3ac8206
Show file tree
Hide file tree
Showing 51 changed files with 581 additions and 410 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/github-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
- name: Test with ruff
run: |
echo `ruff --version`
ruff .
ruff check .
- name: Test with mypy
run : |
mypy pypdf
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
sys.path.insert(0, os.path.abspath("."))
sys.path.insert(0, os.path.abspath("../"))

import pypdf as py_pkg # noqa: E402
import pypdf as py_pkg

shutil.copyfile("../CHANGELOG.md", "meta/CHANGELOG.md")
shutil.copyfile("../CONTRIBUTORS.md", "meta/CONTRIBUTORS.md")
Expand Down
13 changes: 11 additions & 2 deletions make_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def main(changelog_path: str) -> None:
Args:
changelog_path: The location of the CHANGELOG file
"""
changelog = get_changelog(changelog_path)
git_tag = get_most_recent_git_tag()
Expand Down Expand Up @@ -66,7 +67,7 @@ def print_instructions(new_version: str) -> None:
print("=" * 80)
print(f"☑ {VERSION_FILE_PATH} was adjusted to '{new_version}'")
print(f"☑ {CHANGELOG_FILE_PATH} was adjusted")
print("")
print()
print("Now run:")
print(" git commit -eF RELEASE_COMMIT_MSG.md")
print(" git push")
Expand Down Expand Up @@ -149,6 +150,7 @@ def version_bump(git_tag: str) -> str:
Returns:
The new version where the patch version is bumped.
"""
# just assume a patch version change
major, minor, patch = git_tag.split(".")
Expand All @@ -164,6 +166,7 @@ def get_changelog(changelog_path: str) -> str:
Returns:
Data of the CHANGELOG
"""
with open(changelog_path, encoding="utf-8") as fh:
changelog = fh.read()
Expand All @@ -177,6 +180,7 @@ def write_changelog(new_changelog: str, changelog_path: str) -> None:
Args:
new_changelog: Contents of the new CHANGELOG
changelog_path: Path where the CHANGELOG file is
"""
with open(changelog_path, "w", encoding="utf-8") as fh:
fh.write(new_changelog)
Expand All @@ -191,6 +195,7 @@ def get_formatted_changes(git_tag: str) -> Tuple[str, str]:
Returns:
Changes done since git_tag
"""
commits = get_git_commits_since_tag(git_tag)

Expand Down Expand Up @@ -266,6 +271,7 @@ def get_most_recent_git_tag() -> str:
Returns:
Most recently created git tag.
"""
git_tag = str(
subprocess.check_output(
Expand All @@ -285,12 +291,13 @@ def get_author_mapping(line_count: int) -> Dict[str, str]:
Returns:
A mapping of long commit hashes to author login handles.
"""
per_page = min(line_count, 100)
page = 1
mapping: Dict[str, str] = {}
for _ in range(0, line_count, per_page):
with urllib.request.urlopen( # noqa: S310
with urllib.request.urlopen(
f"https://api.github.com/repos/{GH_ORG}/{GH_PROJECT}/commits?per_page={per_page}&page={page}"
) as response:
commits = json.loads(response.read())
Expand All @@ -310,6 +317,7 @@ def get_git_commits_since_tag(git_tag: str) -> List[Change]:
Returns:
List of all changes since git_tag.
"""
commits = (
subprocess.check_output(
Expand Down Expand Up @@ -342,6 +350,7 @@ def parse_commit_line(line: str, authors: Dict[str, str]) -> Change:
Raises:
ValueError: The commit line is not well-structured
"""
parts = line.strip().strip('"\\').split(":::")
if len(parts) != 3:
Expand Down
1 change: 1 addition & 0 deletions mutmut_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def pre_mutation(context: Context) -> None:
Args:
context: A mutmut Context object
"""
line = context.current_source_line.strip()
if (
Expand Down
2 changes: 2 additions & 0 deletions pypdf/_cmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def build_char_map(
Returns:
Font sub-type, space_width criteria (50% of width), encoding, map character-map, font-dictionary.
The font-dictionary itself is suitable for the curious.
"""
ft: DictionaryObject = obj["/Resources"]["/Font"][font_name] # type: ignore
font_subtype, font_halfspace, font_encoding, font_map = build_char_map_from_dict(
Expand All @@ -49,6 +50,7 @@ def build_char_map_from_dict(
Returns:
Font sub-type, space_width criteria(50% of width), encoding, map character-map.
The font-dictionary itself is suitable for the curious.
"""
font_type = cast(str, ft["/Subtype"].get_object())
encoding, map_dict = get_encoding(ft)
Expand Down
2 changes: 2 additions & 0 deletions pypdf/_codecs/_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def encode(self, data: bytes) -> bytes:
Returns:
Encoded data.
"""

@abstractmethod
Expand All @@ -35,6 +36,7 @@ def decode(self, data: bytes) -> bytes:
Returns:
Decoded data.
"""


Expand Down
16 changes: 15 additions & 1 deletion pypdf/_doc_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ def get_num_pages(self) -> int:
Raises:
PdfReadError: if file is encrypted and restrictions prevent
this action.
"""
# Flattened pages will not work on an encrypted PDF;
# the PDF file's page count is used in this case. Otherwise,
Expand All @@ -365,6 +366,7 @@ def get_page(self, page_number: int) -> PageObject:
Returns:
A :class:`PageObject<pypdf._page.PageObject>` instance.
"""
if self.flattened_pages is None:
self._flatten(self._readonly)
Expand Down Expand Up @@ -468,6 +470,7 @@ def _get_named_destinations(
Returns:
A dictionary which maps names to
:class:`Destinations<pypdf.generic.Destination>`.
"""
if retval is None:
retval = {}
Expand Down Expand Up @@ -550,6 +553,7 @@ def get_fields(
value is a :class:`Field<pypdf.generic.Field>` object. By
default, the mapping name is used for keys.
``None`` if form data could not be located.
"""
field_attributes = FA.attributes_dict()
field_attributes.update(CheckboxRadioButtonAttributes.attributes_dict())
Expand Down Expand Up @@ -700,6 +704,7 @@ def get_form_text_fields(self, full_qualified_name: bool = False) -> Dict[str, A
If the document contains multiple form fields with the same name, the
second and following will get the suffix .2, .3, ...
"""

def indexed_key(k: str, fields: Dict[Any, Any]) -> str:
Expand Down Expand Up @@ -745,6 +750,7 @@ def get_pages_showing_field(
- Multi-page list:
Field with multiple kids widgets
(example: radio buttons, field repeated on multiple pages).
"""

def _get_inherited(obj: DictionaryObject, key: str) -> Any:
Expand Down Expand Up @@ -806,6 +812,7 @@ def open_destination(
Raises:
Exception: If a destination is invalid.
"""
if "/OpenAction" not in self.root_object:
return None
Expand Down Expand Up @@ -917,6 +924,7 @@ def get_page_number(self, page: PageObject) -> Optional[int]:
Returns:
The page number or None if page is not found
"""
return self._get_page_number_by_indirect(page.indirect_reference)

Expand All @@ -929,6 +937,7 @@ def get_destination_page_number(self, destination: Destination) -> Optional[int]
Returns:
The page number or None if page is not found
"""
return self._get_page_number_by_indirect(destination.page)

Expand Down Expand Up @@ -962,7 +971,7 @@ def _build_destination(
# create a link to first Page
tmp = self.pages[0].indirect_reference
indirect_reference = NullObject() if tmp is None else tmp
return Destination(title, indirect_reference, Fit.fit()) # type: ignore
return Destination(title, indirect_reference, Fit.fit())

def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]:
dest, title, outline_item = None, None, None
Expand Down Expand Up @@ -1135,6 +1144,7 @@ def _flatten(
pages:
inherit:
indirect_reference: Used recursively to flatten the /Pages object.
"""
inheritable_page_attributes = (
NameObject(PG.RESOURCES),
Expand Down Expand Up @@ -1208,6 +1218,7 @@ def remove_page(
clean: replace PageObject with NullObject to prevent annotations
or destinations to reference a detached page.
"""
if self.flattened_pages is None:
self._flatten(self._readonly)
Expand Down Expand Up @@ -1246,6 +1257,7 @@ def _get_indirect_object(self, num: int, gen: int) -> Optional[PdfObject]:
Returns:
A PdfObject
"""
return IndirectObject(num, gen, self).get_object()

Expand Down Expand Up @@ -1333,6 +1345,7 @@ def _list_attachments(self) -> List[str]:
Returns:
list of filenames
"""
catalog = self.root_object
# From the catalog get the embedded file names
Expand Down Expand Up @@ -1371,6 +1384,7 @@ def _get_attachments(
Returns:
dictionary of filename -> Union[bytestring or List[ByteString]]
If the filename exists multiple times a list of the different versions will be provided.
"""
catalog = self.root_object
# From the catalog get the embedded file names
Expand Down
13 changes: 13 additions & 0 deletions pypdf/_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def compute_key(
Returns:
The u_hash digest of length key_size
"""
a = _padding(password)
u_hash = hashlib.md5(a)
Expand Down Expand Up @@ -243,6 +244,7 @@ def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes
Returns:
The RC4 key
"""
a = _padding(owner_password)
o_hash_digest = hashlib.md5(a).digest()
Expand All @@ -266,6 +268,7 @@ def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes:
Returns:
The RC4 encrypted
"""
a = _padding(user_password)
rc4_enc = rc4_encrypt(rc4_key, a)
Expand Down Expand Up @@ -297,6 +300,7 @@ def compute_U_value(key: bytes, rev: int, id1_entry: bytes) -> bytes:
Returns:
The value
"""
if rev <= 2:
value = rc4_encrypt(key, _PADDING)
Expand Down Expand Up @@ -381,6 +385,7 @@ def verify_user_password(
Returns:
The key
"""
key = AlgV4.compute_key(
user_password, rev, key_size, o_entry, P, id1_entry, metadata_encrypted
Expand Down Expand Up @@ -443,6 +448,7 @@ def verify_owner_password(
Returns:
bytes
"""
rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size)

Expand Down Expand Up @@ -526,6 +532,7 @@ def verify_owner_password(
Returns:
The key
"""
password = password[:127]
if (
Expand Down Expand Up @@ -556,6 +563,7 @@ def verify_user_password(
Returns:
bytes
"""
password = password[:127]
if AlgV5.calculate_hash(R, password, u_value[32:40], b"") != u_value[:32]:
Expand Down Expand Up @@ -605,6 +613,7 @@ def verify_perms(
Returns:
A boolean
"""
b8 = b"T" if metadata_encrypted else b"F"
p1 = struct.pack("<I", p) + b"\xff\xff\xff\xff" + b8 + b"adb"
Expand Down Expand Up @@ -658,6 +667,7 @@ def compute_U_value(R: int, password: bytes, key: bytes) -> Tuple[bytes, bytes]:
Returns:
A tuple (u-value, ue value)
"""
random_bytes = secrets.token_bytes(16)
val_salt = random_bytes[:8]
Expand Down Expand Up @@ -702,6 +712,7 @@ def compute_O_value(
Returns:
A tuple (O value, OE value)
"""
random_bytes = secrets.token_bytes(16)
val_salt = random_bytes[:8]
Expand Down Expand Up @@ -745,6 +756,7 @@ def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes:
Returns:
The perms value
"""
b8 = b"T" if metadata_encrypted else b"F"
rr = secrets.token_bytes(4)
Expand Down Expand Up @@ -798,6 +810,7 @@ class Encryption:
encrypting embedded file streams that do not have their own
crypt filter specifier.
values: Additional encryption parameters.
"""

def __init__(
Expand Down
Loading

0 comments on commit 3ac8206

Please sign in to comment.