Aller au contenu

Analyser et inspecter un PDF pour en extraire des faits structurels

Cette recette lit les faits structurels d’un PDF au moyen du repli Quick de l’inspecteur Core : version, nombre de pages, indicateurs de chiffrement, de signature et de pièces jointes, taille du fichier et indicateurs de risque. Quick s’exécute entièrement dans le processus, sans sidecar Spectrum ni réseau. Utilise-le pour un tri rapide, pas comme validateur.

Fenêtre de terminal
composer require nextpdf/core:^3

Un PDF enregistre sa version dans l’en-tête du fichier (ISO 32000-2 §7.5.2). La section finale (trailer) contient un identifiant de fichier (/ID) composé de deux chaînes d’octets (ISO 32000-2 §7.5.5). Lorsqu’une signature est présente, elle prend la forme d’un dictionnaire de signature dont l’entrée Contents contient des données CMS SignedData au format DER (ISO 32000-2 §12.8.1). Le repli Quick dérive la version, une estimation du nombre de pages ainsi que les indicateurs de présence de chiffrement, de signature et de pièces jointes au moyen d’une analyse bornée des octets du document.

Appelle new Inspector(), puis ->inspect(string $pdfData, InspectConfig::quick()). Le résultat est un InspectResult qui expose $pdfVersion, $pageCount, $isEncrypted, $hasSigned, $hasAttachments, $fileSizeBytes, $riskFlags, et l’utilitaire hasRisks().

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Inspect\InspectConfig;
use NextPDF\Inspect\Inspector;
$pdf = file_get_contents(__DIR__ . '/document.pdf');
$result = (new Inspector())->inspect($pdf, InspectConfig::quick());
printf(
"v%s, %d page(s), encrypted=%s, signed=%s\n",
$result->pdfVersion ?? '?',
$result->pageCount,
$result->isEncrypted ? 'yes' : 'no',
$result->hasSigned ? 'yes' : 'no',
);

Voici le programme autonome, exécutable par le harnais. Il reflète examples/39-parse-and-inspect-pdf.php : il construit un petit PDF de plusieurs pages en mémoire, lit ses faits structurels grâce au repli Quick et oriente le traitement d’après ces faits (jamais d’après un verdict de confiance). La branche d’orientation est fournie à titre d’illustration. Branche ton propre pipeline, ta file d’attente de vérification et ta mise en quarantaine.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Inspect\InspectConfig;
use NextPDF\Inspect\Inspector;
// A self-contained input so the program runs with no external file.
$doc = Document::createStandalone();
$doc->setTitle('Parse-and-inspect demo');
$doc->setAuthor('NextPDF Cookbook');
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Page one of the parse-and-inspect demonstration.', newLine: true);
$doc->addPage();
$doc->cell(0, 10, 'Page two.', newLine: true);
$pdf = $doc->getPdfData();
$result = (new Inspector())->inspect($pdf, InspectConfig::quick());
echo 'PDF version : ' . ($result->pdfVersion ?? 'unknown') . "\n";
echo 'Pages : ' . $result->pageCount . "\n";
echo 'Encrypted : ' . ($result->isEncrypted ? 'yes' : 'no') . "\n";
echo 'Signed : ' . ($result->hasSigned ? 'yes' : 'no') . "\n";
echo 'Attachments : ' . ($result->hasAttachments ? 'yes' : 'no') . "\n";
echo 'File size : ' . $result->fileSizeBytes . " bytes\n";
echo 'Risk flags : ' . ($result->hasRisks() ? count($result->riskFlags) : 0) . "\n";
// Route on structural facts, not trust verdicts. Replace these calls with
// your own pipeline / verifier queue / quarantine.
if ($result->isEncrypted) {
// $pipeline->decryptThenContinue($pdf);
echo "Route: decrypt-then-continue\n";
} elseif ($result->hasSigned) {
// $verifierQueue->enqueue($pdf); // see the signature-inspect recipe
echo "Route: enqueue for cryptographic verification\n";
} elseif ($result->hasRisks()) {
// $quarantine->hold($pdf, $result->riskFlags);
echo "Route: quarantine (risk flags present)\n";
} else {
// $pipeline->continue($pdf);
echo "Route: continue (no risks, unsigned, unencrypted)\n";
}
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the
// semantic profile; emit the document to the side-channel.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
file_put_contents($out !== false && $out !== '' ? $out : __DIR__ . '/inspected.pdf', $pdf);

Sortie STDOUT attendue (la version et la taille dépendent de la build ; le PDF de démonstration est non chiffré, non signé et sans risque) :

PDF version : <version>
Pages : 2
Encrypted : no
Signed : no
Attachments : no
File size : <n> bytes
Risk flags : 0
Route: continue (no risks, unsigned, unencrypted)
  • Quick relève du tri, pas de la validation. Il indique ce qui est présent et ce qui est absent. Il ne vérifie pas les signatures, ne déchiffre pas le contenu et n’affirme aucune conformité. Traite le résultat comme une donnée d’orientation.
  • Le nombre de pages est une estimation. Le repli Quick compte les marqueurs d’objets de page. Un graphe d’objets délibérément mal formé peut fausser le décompte. Utilise les profondeurs adossées à Spectrum lorsque l’exactitude du décompte compte.
  • Standard/Full nécessitent le sidecar. new InspectConfig() (profondeur Standard) et InspectConfig::full() nécessitent le sidecar Spectrum. Ces configurations lèvent INSPECT-SIDECAR-001 lorsqu’il est indisponible, et elles ne se rabattent pas silencieusement sur Quick.
  • Entrée vide. Une chaîne vide lève une exception d’inspection avec le message « PDF data must not be empty ».
  • Portée de l’indicateur de chiffrement. L’indicateur reflète une entrée /Encrypt de la section finale (trailer). Un fichier marqué ainsi n’est pas déchiffré par l’inspecteur.

Le repli Quick effectue une analyse bornée, pas une analyse complète. Il convient au pré-routage à fort volume des fichiers entrants avant un traitement plus lourd.

L’inspecteur s’exécute dans le processus et ne lit que des marqueurs structurels. Aucun octet du document ne quitte l’hôte, et aucun texte du document n’est extrait. Un indicateur de risque, par exemple du JavaScript intégré, est un signal consultatif pour l’orientation. Il ne constitue pas une affirmation que le fichier est sûr ou dangereux.

ÉnoncéSpécificationArticlereference_id
L’en-tête du fichier enregistre la version du PDF.ISO 32000-2§7.5.2
Le /ID de la section finale est un identifiant de fichier composé de deux chaînes d’octets.ISO 32000-2§7.5.5
L’entrée Contents d’un dictionnaire de signature contient des données CMS SignedData au format DER.ISO 32000-2§12.8.1

Cette recette rapporte des faits structurels. Elle n’affirme pas que le fichier est valide, sûr ou conforme.

Les profondeurs d’inspection Standard et Full passent par le sidecar Spectrum. Elles ajoutent une analyse plus riche des objets, des polices et des images. Le repli Quick documenté ici relève de Core et fonctionne hors ligne.