Åpne en PDF generert av Microsoft Word eller Excel, bla gjennom den, og ingenting ser uvanlig ut. Last den inn i et Delphi-program, les av sidetallet, og tallet er riktig. Lagre den deretter på nytt med kryptering aktivert, og prosessen feiler med en EListError, eller utdatafilen åpnes med en advarsel om skadet kryssreferanse. Filen var aldri korrupt. Det er en hybrid-referansefil, og selve strukturen som lar et femten år gammelt visningsprogram åpne den, er den samme strukturen som knekker en innleser som slutter å lese for tidlig.
Dette er en av de vanligste måtene en PDF-rørledning som besto alle interne tester, møter en fil den ikke kan round-trippe. Testfilene ble alle generert internt, så de var aldri hybride. Den første hybride filen ankommer den dagen en kunde videresender en faktura eksportert fra et regneark.
Hva Word og Excel faktisk skriver
ISO 32000-1 beskriver layouten med hybrid-referanser i §7.5.8.4. Et program som ønsker PDF 1.5-funksjoner som objektstrømmer, men som samtidig vil la en PDF 1.4-leser åpne filen, skriver kryssreferanseinformasjonen to ganger. Det finnes en klassisk kryssreferansetabell, de ASCII-radene med fast bredde som avsluttet alle PDF-er opp til versjon 1.4, og det finnes en kryssreferansestrøm som indekserer resten. Traileren til den klassiske seksjonen inneholder en /XRefStm-oppføring hvis verdi er byte-forskyvningen til denne strømmen.
Arbeidsdelingen er tilsiktet. Objekter en gammel leser må nå, blant annet katalogen og sidetreet, er adresserbare fra den klassiske tabellen. Objekter som ble lagt inn i komprimerte objektstrømmer, er merket som ledige i den klassiske tabellen med en oppføring av type f, slik at en 1.4-leser hopper rett forbi dem og aldri snubler over en struktur den ikke kan tolke. Deres faktiske plasseringer finnes bare i kryssreferansestrømmen. Signaturen til en slik fil finnes i slutten: en kort klassisk seksjon, ofte ikke mer enn xref etterfulgt av en 0 0-underseksjonsoverskrift, hvis trailer peker på den /XRefStm der de faktiske gjenopprettingsdataene ligger.
Hvorfor et riktig sidetall ikke beviser noe
Fordi katalogen og sidetreet med vilje er tilgjengelige fra den klassiske tabellen, vil en innleser som bare leser den tabellen finne /Root, gå gjennom sidetreet og rapportere riktig antall sider. Alt en gammel leser trenger er til stede, så filen virker sunn. Objektene som forsvant, er de som er pakket inn i objektstrømmer: AcroForm-feltordbøker, taggete PDF-strukturelementer, og den lange halen av små ordbøker som aldri trengte å være synlige for et eldre visningsprogram.
Du merker ikke gapet før noe rører ved disse objektene, og en fullstendig re-lagring rører ved dem alle. Å gå gjennom dokumentet for å kryptere eller skrive det på nytt er nettopp den operasjonen som ber om hvert objektnummer etter tur, og det er grunnen til at symptomet dukker opp ved lagring i stedet for ved lasting, langt unna årsaken.
Fellen er en detektor som ser xref og stopper
Den enkle måten å bestemme hvordan en fil er indeksert på, er å følge startxref og inspisere de første bytene den peker på. Nøkkelordet xref betyr en klassisk tabell; et strømobjekt betyr en kryssreferansestrøm. Den testen er riktig for alle filer som forholder seg til én metode. Den er feil for en hybridfil, der startxref peker på en klassisk seksjon kun for å tilfredsstille eldre lesere, mens /XRefStm i denne seksjonens trailer er der det meste av dokumentet faktisk er indeksert. En detektor som returnerer "classic" ved den første xref den møter, leser aldri /XRefStm, og alle objekter som bare finnes i strømmen blir usynlige.
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf'); // count is correct
// inspect or edit the loaded document here
Pdf.SaveLoadedDocument('Invoice_secured.pdf'); // walks every object
finally
Pdf.Free;
end;
end;
Flette-rekkefølgen er ikke forhandlingsbar
Når begge indeksene er lest, kan de bare kombineres i én retning. Kryssreferansestrømmen må flettes først, og de klassiske oppføringene fylles inn rundt den. Årsaken er den lille avsporingen i kjernen av formatet. En hybridfil markerer sine komprimerte objekter som ledige i den klassiske tabellen slik at eldre lesere ignorerer dem. En innleser som følger regelen om at den første som blir funnet vinner, og dermed leser den klassiske tabellen først, vil registrere disse objektnumrene som ledige, for så å forkaste strømoppføringene som faktisk lokaliserer dem, fordi plassene allerede er tatt. Snu rekkefølgen, og type 2-oppføringene fra strømmen, der hver enkelt er et objektstrøm-nummer pluss en indeks, vinner plassene de er ment å eie, og de klassiske oppføringene legger seg rundt dem.
Den samme disiplinen forhindrer at en eldre revisjon gjenoppliver et slettet objekt. Inkrementelle oppdateringer lenker bakover via /Prev, og en ledig oppføring av type 0 is a sentinel that a more recent section has retired an object number. En senere, eldre seksjon i kjeden må ikke få lov til å overskrive denne vaktposten med en utdatert plassering. Behandle første-funnet som autoritativt for ledige markører, og det slettede objektet forblir slettet; behandle det uforsiktig, og filens egen historikk gjenoppliver innhold som den nyeste revisjonen fjernet.
Hva dette betyr i HotPDF
Motoren løser hybrid-referansefiler for deg, og den gjør det på alle baner som må tolke kryssreferansedataene. Last et dokument med LoadFromFile or LoadFromStream, gjør endringene dine, og kall SaveLoadedDocument; eller kjør en engangsoperasjon som EncryptFile som leser inndata og skriver utdata. Uansett leser gjenopprettingen /XRefStm, fletter strømseksjonen før de klassiske oppføringene, og løser objektene som lever i strømmer før skrivingen ramser dem opp. Krypteringsbanen for AES-256 var der problemet først viste seg, fordi kryptering av et dokument skriver om alle objekter og dermed krever at alle objekter allerede er lokalisert.
// One-shot: read the hybrid input, write an AES-256 encrypted copy
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
'owner-secret', '', aes256, [prPrint, prFillAnnotations]);
Detaljen som er verdt å merke seg, ligger oppstrøms for API-et. Filer som kommer fra Word, Excel, PowerPoint, og en lang liste med "Lagre som PDF"-rørledninger, er rutinemessig hybride. En innleser du bare tester mot utdata fra din egen generator, vil kanskje aldri møte en slik under testing. Fyll testdataene dine med dokumenter eksportert fra ekte Office-programmer, ikke bare med filer din egen kode har produsert.
Sjekke en fil du mistenker
To inspeksjoner avklarer spørsmålet raskt. Åpne filen i en heksadesimalvisning og les bytene etter den siste startxref; en hybridfil viser en kort klassisk seksjon hvis trailer-ordbok inneholder /XRefStm. Eller sammenlign objektantallet en programmatisk tolkning rapporterer mot det høyeste objektnummeret som /Size deklarerer i traileren. Et stort gap betyr at objekter gjemmer seg i strømmer innleseren ikke har åpnet, noe som er det samme avviket som blir til en feil under lagring senere.
Skriversiden av denne historien, hvordan objektstrømmer og komprimerte kryssreferanser produseres i utgangspunktet, er dekket i vår artikkel om objektstrømmer og inkrementelle oppdateringer. Når den aktuelle hybridfilen i tillegg er svært stor, lar lastingsteknikkene i gjennomgangen av Direct File-API for store PDF-arbeidsflyter deg inspisere den uten å lese hele filen inn i minnet. Begge passer naturlig sammen med gjenopprettingen beskrevet her, som leveres som en del av HotPDF-komponenten for Delphi og C++Builder sammen med API-ene for lasting, redigering, kryptering og signering som er dekket andre steder på denne bloggen.