Module optionnel · pdftoolkit/pdf-reader 0.7.0

English | Français

Le Reader expose les véritables préfixes sources d’un PDF incrémental chronologique. Ces vues physiques immuables conservent les octets d’origine, les sections xref et les identités d’objets effectives. Elles n’établissent ni confiance dans une signature, ni autorité DocMDP/FieldMDP, ni permission de modifier.

use PDF\Reading\DocumentReader;
use PDF\Reading\FileBytes;

// L’application fournit ces octets et borne leur acquisition.
$history = (new DocumentReader())->readRevisionHistory(new FileBytes($bytes));
foreach ($history as $revision) {
    $raw = $revision->rawDocument();
    $exactPrefix = $revision->source();
    $xrefOffset = $revision->crossReference->section->offset->value;
    $physicalTrailer = $revision->crossReference->section->trailer;
    $effectiveTrailer = $raw->trailer();
    $effectiveEntries = $raw->crossReferences()->entries();
}

ReadRevisionHistory est comptable et itérable, de l’ancienne révision à la plus récente, avec des ordinaux commençant à zéro et latest(). Sa propriété source conserve tous les octets fournis. Chaque ReadDocument brut résout les objets uniquement avec l’index effectif de son préfixe : les remplacements ultérieurs ne contaminent pas les anciens graphes ; entrées libres et générations réutilisées gardent leur sémantique. PhysicalCrossReference conserve la section, sa fin lexicale endOffset, le booléen stream et l’éventuel supplément hybride auxiliary. La table porte /Prev ; les entrées du supplément prévalent.

Un fichier initial linéarisé possède deux sections xref, mais une seule révision physique complète. Sa section de première page porte crossReference ; linearized conserve la section principale, l’éventuel footer factice de première page, l’étendue source du dictionnaire de linéarisation et sa longueur déclarée L. physicalParts() retourne les corps xref exacts, suppléments hybrides compris. Un L valide fixe la longueur du préfixe initial dans son véritable intervalle EOF, même si les mises à jour ajoutent des blancs. Ce regroupement physique n’interprète pas les flux de hints et ne certifie pas une distribution réseau optimisée.

EOF exact et longueurs signées

Chaque RevisionBoundary conserve cinq offsets absolus :

Propriété Sens
footerOffset Premier octet du véritable mot-clé startxref, après les éventuels objets indexés finaux.
eofOffset Premier % du marqueur %%EOF vérifié.
eofMarkerEnd Premier octet après ses cinq caractères ; longueur minimale admissible.
endOffset Fin conventionnelle du préfixe, incluant la première fin de ligne du marqueur si présente ; une révision initiale linéarisée utilise son L vérifié.
maximumEndOffset Premier octet suivant qui n’est pas un blanc, ou longueur totale de la source.

acceptsByteLength($length) contrôle l’intervalle inclusif entre eofMarkerEnd et maximumEndOffset. Cet intervalle ne contient que des blancs PDF. source() retourne le préfixe conventionnel ; source($length) retourne exactement ce préfixe admissible, sans retirer, ajouter ni normaliser d’octet. Une longueur hors intervalle lève ParseException.

Un vérificateur de signature doit choisir la longueur du véritable ByteRange, lier cette source exacte et valider séparément les plages, Contents et CMS. Correspondre à un intervalle EOF n’établit aucun de ces faits. Cette opération ne choisit pas les octets signés et ne crée pas de FileRevision du writer.

Objets bruts et authentification

ReadDocument::crossReferences()->entries() expose les entrées effectives, y compris libres et indisponibles. objectSpan($identifier) donne le SourceObjectSpan d’un objet ordinaire, de son véritable header jusqu’à endobj. Il retourne null pour les membres compressés, objets absents/libres et mauvaises générations. Les membres compressés ont des positions dans des conteneurs décodés, sans étendue indépendante dans le fichier source.

rawDocument()->sourceRevision()->span($string) expose la provenance du jeton chaîne établie par le parseur, délimiteurs compris pour les Contents d’une signature. Une chaîne arbitraire de même valeur n’acquiert pas cette provenance. Les Contents de signature gardent aussi leur étendue d’origine après une lecture authentifiée séparée. Une chaîne ordinaire déchiffrée n’atteste pas la présence d’un jeton en clair dans la source.

L’inspection physique n’authentifie rien et n’applique pas la politique d’intégrité source. Les chaînes et flux bruts peuvent être chiffrés ; permissions() et mac() restent null. Ce graphe ne constitue pas du contenu authentifié. Pour interpréter un préfixe choisi, appeler explicitement un DocumentReader::read($revision->source($exactLength)) configuré séparément avec le mot de passe ou la capacité de destinataire et la politique d’intégrité requis. Ses contrôles normaux d’authentification et de MAC s’appliquent. Les flux d’objets chiffrés ne sont décodés que dans ce graphe authentifié séparé. L’inspection ne peut pas résoudre une longueur structurelle de flux nécessitant elle-même le décodage d’un flux d’objets chiffré.

Bornes et organisations physiques prises en charge

Toutes les vues d’un historique partagent un ReadBudget : valeurs analysées, entrées xref, données décodées et octets conservés se cumulent entre révisions. Chaque préfixe, index effectif et étendue d’objet ordinaire conservé est compté, ainsi que les copies supplémentaires de préfixes exacts. Le préfixe conventionnel en cache n’est pas recopié. ReaderLimits::maxRevisions et les limites existantes d’analyse/décompression s’appliquent. Une lecture authentifiée read() reste une opération séparée avec son budget ; l’application borne le nombre de ces appels.

L’inspection contrôle les étendues physiques de tous les objets ordinaires effectifs, versions historiques comprises, avant de retourner l’historique. Elles ne peuvent chevaucher d’autres objets, les corps xref ou les footers. Les octets entre étendues connues doivent être des blancs ou commentaires ; un faux header d’objet dans une chaîne, un flux ou un commentaire ne peut revendiquer un objet indépendant. Une ancienne révision ne peut adresser des octets futurs. La syntaxe non indexée et un footer ajouté pointant sur un ancien xref sont refusés.

L’organisation prise en charge est une chaîne /Prev chronologique de tables, flux ou sections hybrides. Des objets ordinaires indexés peuvent suivre le flux xref principal avant son footer ; leurs étendues réelles, y compris les longueurs indirectes de flux, sont vérifiées avant de localiser startxref. La paire initiale linéarisée est reconnue à partir de son véritable premier objet et de ses paramètres directs bornés. Son /Prev vers l’avant doit désigner exactement la section principale, sans autre /Prev ; son footer final désigne la section de première page. Chaînes arbitraires progressant vers l’avant, objets non indexés et commentaires dans le footer restent hors de cette API d’inspection. Certaines de ces organisations sont des PDF valides ; read() conserve son comportement. Ce service fournit une inspection structurelle bornée, pas une validation générale de conformité ni une réparation.

Validation indépendante

tools/validate_revision_history.py produit des historiques table, stream et hybrides, en clair et chiffrés AES-256, avec les API pyHanko non modifiées. Chacun contient une révision initiale, une véritable signature d’approbation CMS et une mise à jour ultérieure d’objet. La source hybride initiale a une vraie table associée et /XRefStm ; sa signature est créée ensuite. Une organisation initiale assemblée séparément ajoute des objets indexés après le flux xref, dont un flux de longueur indirecte ; pyHanko non modifié signe et met aussi à jour cette source. Quatre fichiers initiaux sont linéarisés indépendamment par qpdf (tables/flux, clair/chiffré), puis signés et mis à jour par pyHanko. PyHanko compte séparément leurs deux sections xref initiales ; l’oracle les associe explicitement à une seule révision physique complète. Cinq fixtures pypdf/qpdf existantes ajoutent suppression et réutilisation de génération. Les 42 préfixes des 16 sources passent qpdf sans récupération. PyHanko contrôle indépendamment les offsets xref des révisions, la résolution d’objets et 22 vérifications mathématiques de signatures. La validation est hors ligne sans racine de confiance ; la confiance dans les certificats et l’autorisation des modifications ultérieures ne sont volontairement pas attestées.

Les tests natifs ajoutent offsets/footers falsifiés, identités chevauchantes, références futures, accès brut chiffré, provenance Contents et budgets cumulés. La CI exécute cet oracle et les contrôles existants sur PHP 8.3, 8.4 et 8.5. Les clauses sources et le périmètre figurent dans la correspondance normative.

Composants · Sécurité · MAC source

Source de ce guide au commit documenté ↗