pyhanko.sign.diff_analysis.commons module

Module defining common helpers for use by rules and policies.

In principle, these aren’t relevant to the high-level validation API.

pyhanko.sign.diff_analysis.commons.qualify(level: ~pyhanko.sign.diff_analysis.policy_api.ModificationLevel, rule_result: ~typing.Generator[~pyhanko.sign.diff_analysis.commons.X, None, ~pyhanko.sign.diff_analysis.commons.R], transform: ~typing.Callable[[~pyhanko.sign.diff_analysis.commons.X], ~pyhanko.sign.diff_analysis.rules_api.ReferenceUpdate] = <function <lambda>>) Generator[Tuple[ModificationLevel, ReferenceUpdate], None, R]

This is a helper function for rule implementors. It attaches a fixed modification level to an existing reference update generator, respecting the original generator’s return value (if relevant).

A prototypical use would be of the following form:

def some_generator_function():
    # do stuff
    for ref in some_list:
        # do stuff
        yield ref

    # do more stuff
    return summary_value

# ...

def some_qualified_generator_function():
    summary_value = yield from qualify(

Provided that some_generator_function yields ReferenceUpdate objects, the yield type of the resulting generator will be tuples of the form (level, ref).

  • level – The modification level to set.

  • rule_result – A generator that outputs references to be whitelisted.

  • transform – Function to apply to the reference object before appending the modification level and yielding it. Defaults to the identity.


A converted generator that outputs references qualified at the modification level specified.

pyhanko.sign.diff_analysis.commons.safe_whitelist(old: HistoricalResolver, old_ref, new_ref) Generator[Reference, None, None]

Checks whether an indirect reference in a PDF structure can be updated without clobbering an older object in a way that causes ramifications at the PDF syntax level.

The following are verified:

  • Does the old reference point to a non-stream object?

  • If the new reference is equal to the old one, does the new reference point to a non-stream object?

  • If the new reference is not equal to the old one, is the new reference a newly defined object?

This is a generator for syntactical convenience and integration with internal APIs, but it will always yield at most one element.

pyhanko.sign.diff_analysis.commons.compare_key_refs(key, old: HistoricalResolver, old_dict: DictionaryObject, new_dict: DictionaryObject) Generator[Reference, None, Tuple[Optional[PdfObject], Optional[PdfObject]]]

Ensure that updating a key in a dictionary has no undesirable side effects. The following scenarios are allowed:

  1. replacing a direct value with another direct value

  2. adding a key in new_dict

  3. replacing a direct value in old_dict with a reference in new_dict

  4. the reverse (allowed by default)

  5. replacing a reference with another reference (that doesn’t override anything else)

The restrictions of safe_whitelist apply to this function as well.

Note: this routine is only safe to use if the structure of the resulting values is also checked. Otherwise, it can lead to reference leaks if one is not careful.

pyhanko.sign.diff_analysis.commons.compare_dicts(old_dict: PdfObject, new_dict: PdfObject, ignored: Set[str] = frozenset({}), raise_exc=True) bool

Compare entries in two dictionaries, optionally ignoring certain keys.


Throw SuspiciousModification if the argument is a stream object.