U
    c                     @   s^  d Z ddlZddlmZmZmZmZmZmZ ddl	m
Z
mZ ddlmZmZ ddlmZ ddlmZmZ dd	lmZ eeZd
ddddgZeee
j ee
j f Zdd Zeeeddf dddZee
je
jeedef dddZedZedZ dd feee def ee gef eeeef def ddd
Z!e" dfeeee# e$dddZ%dS )z
Module defining common helpers for use by rules and policies.

In principle, these aren't relevant to the high-level validation API.
    N)Callable	GeneratorOptionalSetTupleTypeVar)genericmisc)	PdfObject	Reference)HistoricalResolver   )ModificationLevelSuspiciousModification)ReferenceUpdatequalifysafe_whitelistcompare_key_refscompare_dictsassert_not_streamc                 C   s"   t | tjrtd| j ddS )zT
    Throw :class:`.SuspiciousModification` if the argument is a stream object.
    z!Unexpected stream encountered at !N)
isinstancer   ZStreamObjectr   Zcontainer_ref)obj r   F/tmp/pip-unpacked-wheel-0kb_yl26/pyhanko/sign/diff_analysis/commons.pyr      s    )oldreturnc                 c   sR   |rt |  ||kr,t |  |V  n"| |r>|V  ntd| ddS )a~  
    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.
    zUpdate clobbers or reuses z in an unexpected way.N)r   
get_objectZis_ref_availabler   )r   Zold_refZnew_refr   r   r   r   &   s    

)r   old_dictnew_dictr   c                 c   s   z.| | }t|tjr(|j}| }nd}W n tk
rJ   d }}Y nX z.| | }t|tjrt|j}| }nd}W n. tk
r   |dk	rtd|  dY dS X |dk	rt|||E dH  ||fS )a  
    Ensure that updating a key in a dictionary has no undesirable side effects.
    The following scenarios are allowed:

    0. replacing a direct value with another direct value
    1. adding a key in new_dict
    2. replacing a direct value in old_dict with a reference in new_dict
    3. the reverse (allowed by default)
    4. 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.
    NzKey z was deleted from dictionary)NN)	raw_getr   r   ZIndirectObject	referencer   KeyErrorr   r   )keyr   r   r   	old_valueZold_value_ref	new_valueZnew_value_refr   r   r   r   G   s.    




RXc                 C   s   | S Nr   )xr   r   r   <lambda>       r*   )levelrule_result	transformr   c                    s   t | fddS )aL  
    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:

    .. code-block:: python

        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(
                ModificationLevel.FORM_FILLING,
                some_generator_function()
            )

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

    :param level:
        The modification level to set.
    :param rule_result:
        A generator that outputs references to be whitelisted.
    :param transform:
        Function to apply to the reference object before appending
        the modification level and yielding it.
        Defaults to the identity.
    :return:
        A converted generator that outputs references qualified at the
        modification level specified.
    c                    s    | fS r(   r   )refr,   r.   r   r   r*      r+   zqualify.<locals>.<lambda>)r	   Zmap_with_return)r,   r-   r.   r   r0   r   r      s    . T)r   r   ignoredr   c           	      C   s   t | tjstdt |tjs*tdt|  t| t| | }t|  | }||kr|r~td| d| dndS |D ]F}|	|}| 	|}||kr|rtd| d| d	| q dS qd
S )zP
    Compare entries in two dictionaries, optionally ignoring certain keys.
    z?Encountered unexpected non-dictionary object in prior revision.z.Dict is overridden by non-dict in new revisionzDict keys differ: z vs. .FzValues for dict key z differ:z changed to T)
r   r   DictionaryObjectr	   ZPdfReadErrorr   r   setkeysr    )	r   r   r1   Z	raise_excZnew_dict_keysZold_dict_keysknew_valZold_valr   r   r   r      s8    

)&__doc__loggingtypingr   r   r   r   r   r   Zpyhanko.pdf_utilsr   r	   Zpyhanko.pdf_utils.genericr
   r   Zpyhanko.pdf_utils.readerr   Z
policy_apir   r   Z	rules_apir   	getLogger__name__logger__all__ZTwoVersionsr   r   r3   r   r&   r'   r   	frozensetstrboolr   r   r   r   r   <module>   sL    
   
!44  