Skip to content

Commit

Permalink
DOC: Use google style docstrings
Browse files Browse the repository at this point in the history
The Google Docstring style is way more readable than ReST style docstrings.

Additionally, replace most instances of 'pragma: no-cover' by
'deprecated'.

Tools used:
- pyment
- pydocstyle
  • Loading branch information
MartinThoma committed Jan 6, 2023
1 parent ea598dd commit 2c1277c
Show file tree
Hide file tree
Showing 24 changed files with 1,071 additions and 480 deletions.
11 changes: 6 additions & 5 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
"""
Configuration file for the Sphinx documentation builder.
This file only contains a selection of the most common options. For a full
list see the documentation:
https://www.sphinx-doc.org/en/master/usage/configuration.html
"""
# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
Expand Down
7 changes: 7 additions & 0 deletions make_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,27 @@ def main(changelog_path: str):


def version_bump(git_tag: str) -> str:
"""Increase the patch version of the git tag by one."""
# just assume a patch version change
major, minor, patch = git_tag.split(".")
return f"{major}.{minor}.{int(patch) + 1}"


def get_changelog(changelog_path: str) -> str:
"""Read the changelog."""
with open(changelog_path) as fh:
changelog = fh.read()
return changelog


def write_changelog(new_changelog: str, changelog_path: str) -> None:
"""Write the changelog."""
with open(changelog_path, "w") as fh:
fh.write(new_changelog)


def get_formatted_changes(git_tag: str) -> str:
"""Format the changes done since the last tag."""
commits = get_git_commits_since_tag(git_tag)

# Group by prefix
Expand Down Expand Up @@ -100,6 +104,7 @@ def get_formatted_changes(git_tag: str) -> str:


def get_most_recent_git_tag():
"""Get the git tag most recently created."""
git_tag = str(
subprocess.check_output(
["git", "describe", "--abbrev=0"], stderr=subprocess.STDOUT
Expand All @@ -109,6 +114,7 @@ def get_most_recent_git_tag():


def get_git_commits_since_tag(git_tag) -> List[Change]:
"""Get all commits since the last tag."""
commits = str(
subprocess.check_output(
[
Expand All @@ -125,6 +131,7 @@ def get_git_commits_since_tag(git_tag) -> List[Change]:


def parse_commit_line(line) -> Change:
"""Parse the first line of a git commit message."""
if "\\t" not in line:
raise ValueError(f"Invalid commit line: {line}")
commit_hash, rest = line.split("\\t", 1)
Expand Down
1 change: 1 addition & 0 deletions mutmut_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@


def pre_mutation(context):
"""Filter what to mutate."""
line = context.current_source_line.strip()
if (
"_codecs" in context.filename
Expand Down
163 changes: 160 additions & 3 deletions pypdf/_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ def compute_key(
for security handlers of revision 2 but, for security handlers of
revision 3 or greater, shall depend on the
value of the encryption dictionary’s Length entry.
Args:
password: The encryption secret as a bytes-string
rev: The encryption revision (see PDF standard)
key_size: The size of the key in bytes
o_entry: The owner entry
P: A set of flags specifying which operations shall be permitted
when the document is opened with user access. If bit 2 is set to 1, all other
bits are ignored and all operations are permitted. If bit 2 is set to 0,
permission for operations are based on the values of the remaining flags
defined in Table 24.
id1_entry:
metadata_encrypted: A boolean indicating if the metadata is encrypted.
Returns:
The u_hash digest of length key_size
"""
a = _padding(password)
u_hash = hashlib.md5(a)
Expand Down Expand Up @@ -335,6 +351,14 @@ def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes
of the iteration counter (from 1 to 19).
h) Store the output from the final invocation of the RC4 function as
the value of the O entry in the encryption dictionary.
Args:
owner_password:
rev: The encryption revision (see PDF standard)
key_size: The size of the key in bytes
Returns:
The RC4 key
"""
a = _padding(owner_password)
o_hash_digest = hashlib.md5(a).digest()
Expand All @@ -348,7 +372,17 @@ def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes

@staticmethod
def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes:
"""See :func:`compute_O_value_key`."""
"""
See :func:`compute_O_value_key`.
Args:
rc4_key:
user_password:
rev: The encryption revision (see PDF standard)
Returns:
The RC4 encrypted
"""
a = _padding(user_password)
rc4_enc = RC4_encrypt(rc4_key, a)
if rev >= 3:
Expand All @@ -371,6 +405,14 @@ def compute_U_value(key: bytes, rev: int, id1_entry: bytes) -> bytes:
function with the encryption key from the preceding step.
c) Store the result of step (b) as the value of the U entry in the
encryption dictionary.
Args:
key:
rev: The encryption revision (see PDF standard)
id1_entry:
Returns:
The value
"""
if rev <= 2:
value = RC4_encrypt(key, _PADDING)
Expand Down Expand Up @@ -432,6 +474,23 @@ def verify_user_password(
dictionary’s U (user password) value (Security handlers of revision 2)" or "Algorithm 5: Computing the
encryption dictionary’s U (user password) value (Security handlers of revision 3 or greater)") shall be used
to decrypt the document.
Args:
user_password: The user passwort as a bytes stream
rev: The encryption revision (see PDF standard)
key_size: The size of the key in bytes
o_entry: The owner entry
u_entry: The user entry
P: A set of flags specifying which operations shall be permitted
when the document is opened with user access. If bit 2 is set to 1, all other
bits are ignored and all operations are permitted. If bit 2 is set to 0,
permission for operations are based on the values of the remaining flags
defined in Table 24.
id1_entry:
metadata_encrypted: A boolean indicating if the metadata is encrypted.
Returns:
The key
"""
key = AlgV4.compute_key(
user_password, rev, key_size, o_entry, P, id1_entry, metadata_encrypted
Expand Down Expand Up @@ -469,6 +528,23 @@ def verify_owner_password(
between each byte of the key and the single-byte value of the iteration counter (from 19 to 0).
c) The result of step (b) purports to be the user password. Authenticate this user password using "Algorithm 6:
Authenticating the user password". If it is correct, the password supplied is the correct owner password.
Args:
owner_password:
rev: The encryption revision (see PDF standard)
key_size: The size of the key in bytes
o_entry: The owner entry
u_entry: The user entry
P: A set of flags specifying which operations shall be permitted
when the document is opened with user access. If bit 2 is set to 1, all other
bits are ignored and all operations are permitted. If bit 2 is set to 0,
permission for operations are based on the values of the remaining flags
defined in Table 24.
id1_entry:
metadata_encrypted: A boolean indicating if the metadata is encrypted.
Returns:
bytes
"""
rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size)

Expand Down Expand Up @@ -524,6 +600,21 @@ def verify_owner_password(
the file encryption key as the key. Verify that bytes 9-11 of the result are the characters ‘a’, ‘d’, ‘b’. Bytes
0-3 of the decrypted Perms entry, treated as a little-endian integer, are the user permissions. They
should match the value in the P key.
Args:
R: A number specifying which revision of the standard security
handler shall be used to interpret this dictionary
password: The owner password
o_value: A 32-byte string, based on both the owner and user passwords,
that shall be used in computing the encryption key and in determining
whether a valid owner password was entered
oe_value:
u_value: A 32-byte string, based on the user password, that shall be
used in determining whether to prompt the user for a password and, if so,
whether a valid user or owner password was entered.
Returns:
The key
"""
password = password[:127]
if (
Expand All @@ -540,7 +631,21 @@ def verify_owner_password(
def verify_user_password(
R: int, password: bytes, u_value: bytes, ue_value: bytes
) -> bytes:
"""See :func:`verify_owner_password`."""
"""
See :func:`verify_owner_password`.
Args:
R: A number specifying which revision of the standard security
handler shall be used to interpret this dictionary
password: The user password
u_value: A 32-byte string, based on the user password, that shall be
used in determining whether to prompt the user for a password and, if so,
whether a valid user or owner password was entered.
ue_value:
Returns:
bytes
"""
password = password[:127]
if AlgV5.calculate_hash(R, password, u_value[32:40], b"") != u_value[:32]:
return b""
Expand Down Expand Up @@ -573,7 +678,22 @@ def calculate_hash(R: int, password: bytes, salt: bytes, udata: bytes) -> bytes:
def verify_perms(
key: bytes, perms: bytes, p: int, metadata_encrypted: bool
) -> bool:
"""See :func:`verify_owner_password` and :func:`compute_Perms_value`."""
"""
See :func:`verify_owner_password` and :func:`compute_perms_value`.
Args:
key: The owner password
perms:
p: A set of flags specifying which operations shall be permitted
when the document is opened with user access. If bit 2 is set to 1, all other
bits are ignored and all operations are permitted. If bit 2 is set to 0,
permission for operations are based on the values of the remaining flags
defined in Table 24.
metadata_encrypted:
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"
p2 = AES_ECB_decrypt(key, perms)
Expand Down Expand Up @@ -610,6 +730,13 @@ def compute_U_value(password: bytes, key: bytes) -> Tuple[bytes, bytes]:
2. Compute the 32-byte SHA-256 hash of the password concatenated with the User Key Salt. Using this
hash as the key, encrypt the file encryption key using AES-256 in CBC mode with no padding and an
initialization vector of zero. The resulting 32-byte string is stored as the UE key.
Args:
password:
key:
Returns:
A tuple (u-value, ue value)
"""
random_bytes = bytes(random.randrange(0, 256) for _ in range(16))
val_salt = random_bytes[:8]
Expand Down Expand Up @@ -637,6 +764,16 @@ def compute_O_value(
concatenated with the 48-byte U string as generated in Algorithm 3.8. Using this hash as the key,
encrypt the file encryption key using AES-256 in CBC mode with no padding and an initialization vector
of zero. The resulting 32-byte string is stored as the OE key.
Args:
password:
key:
u_value: A 32-byte string, based on the user password, that shall be
used in determining whether to prompt the user for a password and, if so,
whether a valid user or owner password was entered.
Returns:
A tuple (O value, OE value)
"""
random_bytes = bytes(random.randrange(0, 256) for _ in range(16))
val_salt = random_bytes[:8]
Expand Down Expand Up @@ -664,6 +801,18 @@ def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes:
6. Encrypt the 16-byte block using AES-256 in ECB mode with an initialization vector of zero, using the file
encryption key as the key. The result (16 bytes) is stored as the Perms string, and checked for validity
when the file is opened.
Args:
key:
p: A set of flags specifying which operations shall be permitted
when the document is opened with user access. If bit 2 is set to 1, all other
bits are ignored and all operations are permitted. If bit 2 is set to 0,
permission for operations are based on the values of the remaining flags
defined in Table 24.
metadata_encrypted: A boolean indicating if the metadata is encrypted.
Returns:
The perms value
"""
b8 = b"T" if metadata_encrypted else b"F"
rr = bytes(random.randrange(0, 256) for _ in range(4))
Expand Down Expand Up @@ -736,6 +885,14 @@ def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObje
block size parameter is set to 16 bytes, and the initialization vector is a 16-byte random number that is
stored as the first 16 bytes of the encrypted stream or string.
The output is the encrypted data to be stored in the PDF file.
Args:
obj:
idnum:
generation:
Returns:
The PdfObject
"""
pack1 = struct.pack("<i", idnum)[:3]
pack2 = struct.pack("<i", generation)[:2]
Expand Down
Loading

0 comments on commit 2c1277c

Please sign in to comment.