Odprite datoteko PDF, ki jo je ustvaril Microsoft Word ali Excel, prelistajte jo in nič ne bo videti nenavadno. Naložite jo v program v Delphiju, preberite število strani in številka bo pravilna. Nato jo ponovno shranite z vklopljenim šifriranjem in opravilo bo spodletelo z napako EListError ali pa se bo izhod odprl z opozorilom o poškodovani navzkrižni referenci (cross-reference). Datoteka ni bila nikoli poškodovana. Gre za datoteko s hibridnimi referencami in prav tista struktura, ki petnajst let staremu pregledovalniku omogoča, da jo odpre, je struktura, ki premaga bralnik, ki prehitro preneha z branjem.
To je eden najpogostejših načinov, ko cevovod PDF, ki je prestal vse interne teste, naleti na datoteko, ki je ne more uspešno krožno predelati (round-trip). Vsi vhodi so bili ustvarjeni interno, zato niso bili nikoli hibridni. Prva hibridna datoteka prispe na dan, ko stranka posreduje račun, izvožen iz preglednice.
Kaj Word in Excel dejansko zapišeta
Standard ISO 32000-1 opisuje postavitev s hibridnimi referencami v §7.5.8.4. Aplikacija, ki želi funkcije PDF 1.5, kot so tokovi objektov (object streams), hkrati pa še vedno omogoča bralniku PDF 1.4, da odpre datoteko, dvakrat zapiše podatke o navzkrižnih referencah. Na voljo je klasična tabela navzkrižnih referenc, to so vrstice ASCII s fiksno širino, ki so končevale vsak PDF do različice 1.4, in tok navzkrižnih referenc, ki indeksira preostale podatke. Zaključek (trailer) klasičnega odseka vsebuje vnos /XRefStm, katerega vrednost je bajtni odmik tega toka.
Delitev dela je namerna. Objekti, ki jih mora doseči star bralnik, med njimi katalog in drevo strani, so naslovljivi iz klasične tabele. Objekti, ki so bili stisnjeni v stisnjene tokove objektov, so v klasični tabeli označeni kot prosti z vnosom tipa f, tako da jih bralnik 1.4 enostavno preskoči in nikoli ne naleti na strukturo, ki je ne more razčleniti. Njihove dejanske lokacije se nahajajo le v toku navzkrižnih referenc. Znak takšne datoteke je njen rep: kratek klasični del, pogosto nič več kot xref, ki mu sledi glava pododdelka 0 0, katere zaključni del kaže na /XRefStm, kjer se nahajajo dejanski obnovitveni podatki.
Zakaj pravilno število strani ne dokazuje ničesar
Ker sta katalog in drevo strani namenoma dosegljiva iz klasične tabele, bralnik, ki bere samo to tabelo, najde /Root, prehodi drevo strani in sporoči pravilno število strani. Vse, kar potrebuje star bralnik, je prisotno, zato se datoteka zdi brezhibna. Objekti, ki so izginili, so tisti, ki so stisnjeni v tokove objektov: slovarji polj AcroForm, strukturni elementi označenih PDF-jev in dolg rep majhnih slovarjev, ki dednemu pregledovalniku nikoli niso bili vidni.
Vrzeli ne opazite, dokler se nekaj ne dotakne teh objektov, popolno ponovno shranjevanje pa se dotakne vseh. Prehod skozi dokument z namenom ponovnega šifriranja ali ponovnega zapisovanja je natanko tista operacija, ki zaporedoma zahteva vsako številko objekta, zato se simptom pojavi ob shranjevanju in ne ob nalaganju, daleč od svojega izvora.
Past je detektor, ki vidi xref in se ustavi
Preprost način za odločitev o tem, kako je datoteka indeksirana, je sledenje oznaki startxref in pregled prvih bajtov, na katere kaže. Ključna beseda xref pomeni klasično tabelo; objekt toka pomeni tok navzkrižnih referenc. Ta preizkus je pravilen za vsako datoteko, ki se drži ene sheme. Napačen pa je za hibridno datoteko, katere startxref kaže na klasični del z edinim namenom, da zadovolji stare bralnike, medtem ko je /XRefStm v zaključnem delu tega odseka tisti, kjer je dejansko indeksiran večji del dokumenta. Detektor, ki ob prvem srečanju z xref vrne "klasično", nikoli ne prebere /XRefStm in vsak objekt, ki živi le v toku, postane neviden.
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;
Z nameščenim detektorjem za predčasni izhod je nalaganje videti v redu, ponovno shranjevanje pa je tisto, kjer se odsotni objekti naznanijo. Rešitev ni branje več bajtov na začetku; gre za to, da prepoznamo hibridni zaključni del (trailer) in sledimo /XRefStm, preden se odločimo, da je delo z datoteko končano.
Vrstni red združevanja ni stvar pogajanja
Ko sta prebrana oba indeksa, ju je mogoče združiti le v eni smeri. Tok navzkrižnih referenc mora biti združen prvi, klasični vnosi pa se zapolnijo okoli njega. Razlog je majhna prevara v osrčju formata. Hibridna datoteka označi svoje stisnjene objekte kot proste v klasični tabeli, da jih stari bralniki prezrejo. Bralnik, ki spoštuje pravilo "prvi viden prevlada" in najprej prebere klasično tabelo, bo te številke objektov zabeležil kot proste, nato pa zavrgel vnose toka, ki jih dejansko locirajo, saj so mesta že zasedena. Obrnite vrstni red in vnosi tipa 2 iz toka, od katerih vsak predstavlja številko toka objektov in indeks, pridobijo mesta, ki jim pripadajo, klasični vnosi pa se razporedijo okoli njih.
Ista disciplina varuje pred tem, da bi starejša revizija obudila izbrisan objekt. Inkrementalne posodobitve se verižijo nazaj prek /Prev, prosti vnos tipa 0 pa je varovalo, da je novejši odsek upokojil številko objekta. Poznejšemu, starejšemu odseku v verigi se ne sme dovoliti, da prepiše to varovalo s staro lokacijo. Če obravnavate prvi viden vnos kot avtoritativen za proste oznake, izbrisan objekt ostane izbrisan; treat it carelessly and a file's own history reanimates content the latest revision removed.
Kaj to pomeni v knjižnici HotPDF
Pogon namesto vas razreši hibridne referenčne datoteke in to stori na vsaki poti, ki mora razčleniti podatke o navzkrižnih referencah. Naložite dokument z metodo LoadFromFile ali LoadFromStream, opravite spremembe in pokličite SaveLoadedDocument; ali pa zaženite enkratno operacijo, kot je EncryptFile, ki prebere vhod in zapiše izhod. V vsakem primeru obnova prebere /XRefStm, združi odsek toka pred klasičnimi vnosi in razreši objekte, ki živijo v tokovih, preden jih zapis popiše. Šifrirna pot AES-256 is tista, kjer se je težava najprej pokazala, saj šifriranje dokumenta znova zapiše vsak objekt in zato zahteva, da so vsi objekti že locirani.
// 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]);
Podrobnost, ki jo je vredno odnesti s seboj, se nahaja pred vmesnikom API. Datoteke, ki prispejo iz programov Word, Excel, PowerPoint in dolgega seznama cevovodov "Shrani kot PDF", so običajno hibridne, zato bralnik, ki ga preizkušate samo z izhodi lastnega generatorja, med testiranjem morda nikoli ne bo naletel nanje. Svoje testne podatke napolnite z dokumenti, izvoženimi iz resničnih pisarniških aplikacij Office, in ne le z datotekami, ki jih je ustvarila vaša koda.
Preverjanje sumljive datoteke
Dva pregleda hitro razrešita vprašanje. Odprite datoteko v heksadecimalnem pogledu in preberite bajte za zadnjim startxref; hibridna datoteka prikazuje kratek klasični del, katerega zaključni slovar vsebuje /XRefStm. Lahko pa primerjate število objektov, o katerih poroča popolno razčlenjevanje, z najvišjo številko objekta, ki jo /Size deklarira v zaključku. Velika vrzel pomeni, da se objekti skrivajo v tokovih, ki jih bralnik ni odprl, kar je ista pomanjkljivost, ki se pozneje spremeni v neuspeh pri shranjevanju.
Stran pisca v tej zgodbi, kako se tokovi objektov in stisnjene navzkrižne reference sploh ustvarijo, je obravnavana v našem članku o tokovih objektov in inkrementalnih posodobitvah. Ko je zadevna hibridna datoteka prav tako zelo velika, vam tehnike nalaganja v vodniku za vmesnik Direct File API za delo z velikimi datotekami PDF omogočajo njen pregled brez nalaganja celotne datoteke v pomnilnik. Obe se naravno dopolnjujeta z obnovo, opisano tukaj, ki se dostavlja kot del komponente HotPDF Component za Delphi in C++Builder, skupaj z vmesniki API za nalaganje, urejanje, šifriranje in podpisovanje, ki so obravnavani drugje na tem blogu.