Otvorte PDF vytvorené v programe Microsoft Word alebo Excel, prechádzajte ním a nič nevyzerá nezvyčajne. Načítajte ho do programu v Delphi, prečítajte počet stránok a číslo je správne. Potom ho znova uložte so zapnutým šifrovaním a úloha zlyhá s chybou EListError, alebo sa výstup otvorí s upozornením na poškodené krížové odkazy (cross-reference). Súbor nebol nikdy poškodený. Ide o súbor s hybridnými referenciami a samotná štruktúra, ktorá umožňuje pätnásťročnému prehliadaču otvoriť ho, je tou istou štruktúrou, ktorá porazí loader, ktorý prestane čítať príliš skoro.
Toto je jeden z najbežnejších spôsobov, ako sa PDF pipeline, ktorá prešla každým interným testom, stretne so súborom, ktorý nedokáže bezpečne spracovať a znova uložiť. Všetky vstupy boli generované interne, takže nikdy neboli hybridné. Prvý hybridný súbor dorazí v deň, keď zákazník prepošle faktúru vygenerovanú z tabuľkového procesora.
Čo Word a Excel v skutočnosti zapisujú
Norma ISO 32000-1 popisuje rozloženie hybridných referencií v §7.5.8.4. Aplikácia, ktorá chce funkcie PDF 1.5, ako sú streamy objektov, a zároveň chce umožniť čítačke PDF 1.4 otvoriť súbor, zapisuje informácie o krížových odkazoch dvakrát. K dispozícii je klasická tabuľka krížových odkazov, riadky ASCII s pevnou šírkou, ktoré končili každé PDF až do verzie 1.4, a stream krížových odkazov, ktorý indexuje zvyšok. Trailer klasickej sekcie obsahuje položku /XRefStm, ktorej hodnotou je bajtový offset daného streamu.
Rozdelenie práce je úmyselné. Objekty, ktoré musí stará čítačka dosiahnuť, vrátane katalógu a stromu stránok, sú adresovateľné z klasickej tabuľky. Objekty, ktoré boli zabalené do komprimovaných streamov objektov, sú v klasickej tabuľke označené ako voľné s položkou typu f, takže čítačka 1.4 ich jednoducho preskočí a nikdy nenarazí na štruktúru, ktorú nedokáže spracovať. Ich skutočné umiestnenie žije iba v streame krížových odkazov. Podpisom takéhoto súboru je jeho koniec: krátka klasická sekcia, často nič viac než len xref nasledovaný hlavičkou podsekcie 0 0, ktorej trailer ukazuje na /XRefStm, kde sedia skutočné obnovovacie dáta.
Prečo správny počet stránok nič nedokazuje
Keďže katalóg a strom stránok sú zámerne dostupné z klasickej tabuľky, loader, ktorý číta iba túto tabuľku, nájde /Root, prejde strom stránok a nahlási správny počet stránok. Všetko, čo stará čítačka potrebuje, je prítomné, takže súbor sa zdá byť zdravý. Chýbajúce objekty sú tie, ktoré sú zabalené v streamoch objektov: slovníky polí AcroForm, prvky štruktúry tagovaného PDF a dlhý chvost malých slovníkov, ktoré pre staršie prehliadače nikdy nemuseli byť viditeľné.
Túto medzeru si nevšimnete, kým sa niečo nedotkne týchto objektov, a úplné opätovné uloženie sa dotkne všetkých z nich. Prechádzanie dokumentu za účelom opätovného zašifrovania alebo prepísania je presne tou operáciou, ktorá si postupne pýta každé číslo objektu, čo je dôvod, prečo sa príznak objaví pri ukladaní a nie pri načítavaní, ďaleko od svojej príčiny.
Pascou je detektor, ktorý uvidí xref a zastaví sa
Jednoduchý spôsob, ako rozhodnúť, ako je súbor indexovaný, je nasledovať startxref a skontrolovať prvé bajty, na ktoré ukazuje. Kľúčové slovo xref znamená klasickú tabuľku; objekt streamu znamená stream krížových odkazov. Tento test is správny pre akýkoľvek súbor, ktorý sa drží jednej schémy. Je však nesprávny pre hybridný súbor, ktorého startxref mieri na klasickú sekciu len za účelom uspokojenia starých čítačiek, zatiaľ čo /XRefStm v traileri tejto sekcie je miestom, kde je v skutočnosti indexovaná väčšina dokumentu. Detektor, ktorý vráti „classic“ pri prvom xref, na ktorý narazí, nikdy neprečíta /XRefStm a každý objekt, ktorý žije iba v streame, sa stane neviditeľným.
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;
S detektorom predčasného ukončenia vyzerá načítanie dobre a chýbajúce objekty sa ohlásia až pri opätovnom uložení. Riešením nie je čítať viac bajtov na začiatku; je to rozpoznanie hybridného trailera a nasledovanie /XRefStm pred rozhodnutím, že súbor je hotový.
Poradie zlúčenia nie je predmetom diskusie
Po prečítaní oboch indexov je možné ich skombinovať iba v jednom smere. Stream krížových odkazov sa musí zlúčiť ako prvý a klasické položky sa musia doplniť okolo neho. Dôvodom je malý klam v srdci formátu. Hybridný súbor označuje svoje komprimované objekty ako voľné v klasickej tabuľke, aby ich staré čítačky ignorovali. Loader, ktorý rešpektuje pravidlo „prvý vyhráva“ a číta najprv klasickú tabuľku, zaznamená tieto čísla objektov ako voľné a potom zahodí položky streamu, ktoré ich skutočne lokalizujú, pretože sloty sú už obsadené. Obráťte poradie a položky typu 2 zo streamu – z ktorých každá predstavuje číslo streamu objektu plus index – získajú sloty, ktoré majú vlastniť, a klasické položky sa usadia okolo nich.
Rovnaká disciplína chráni pred tým, aby staršia revízia oživila vymazaný objekt. Inkrementálne aktualizácie sa reťazia spätne cez /Prev a voľná položka typu 0 je sentinel (strážca), ktorý hovorí, že novšia sekcia vyradila číslo objektu. Nesmie sa dovoliť, aby neskoršia, staršia sekcia v reťazci prepísala tento sentinel zastaraným umiestnením. Považujte prvý nájdený za smerodajný pre voľné značky a vymazaný objekt zostane vymazaný; pristupujte k tomu neopatrne a vlastná história súboru oživí obsah, ktorý najnovšia revízia odstránila.
Čo to znamená v HotPDF
Engine rieši hybridné referenčné súbory za vás, a to na každej ceste, ktorá musí analyzovať dáta krížových odkazov. Načítajte dokument pomocou LoadFromFile alebo LoadFromStream, vykonajte zmeny a zavolajte SaveLoadedDocument; alebo spustite jednorazovú operáciu, ako napríklad EncryptFile, ktorá načíta vstup a zapíše výstup. V každom prípade obnova prečíta /XRefStm, zlúči sekciu streamu pred klasickými položkami a vyrieši objekty žijúce v streamoch ešte pred tým, ako ich zápis začne enumerovať. Šifrovacia cesta AES-256 je miestom, kde sa problém prvýkrát ukázal, pretože šifrovanie dokumentu prepisuje každý objekt, a preto vyžaduje, aby každý objekt už bol lokalizovaný.
// 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]);
Detail, ktorý si stojí za to odniesť, sa nachádza pred samotným API. Súbory pochádzajúce z Wordu, Excelu, PowerPointu a dlhého zoznamu ciest typu „Uložiť ako PDF“ sú bežne hybridné, takže loader, ktorý testujete iba proti výstupu vlastného generátora, sa s nimi pri testovaní nemusí nikdy stretnúť. Naplňte svoje testovacie vzorky dokumentmi exportovanými z reálnych kancelárskych aplikácií Office, nielen súbormi, ktoré vyprodukoval váš vlastný kód.
Kontrola podozrivého súboru
Dve kontroly vyriešia otázku rýchlo. Otvorte súbor v hexadecimálnom zobrazení a prečítajte bajty za posledným startxref; hybridný súbor ukazuje krátku klasickú sekciu, ktorej slovník trailer obsahuje /XRefStm. Alebo porovnajte počet objektov, ktorý nahlási úplná analýza, s najvyšším číslom objektu, ktoré deklaruje /Size v traileri. Veľká medzera znamená, že objekty sa skrývajú v streamoch, ktoré loader neotvoril, čo je rovnaký nedostatok, ktorý sa neskôr zmení na zlyhanie pri ukladaní.
Strana zapisovača v tomto príbehu, ako sa vlastne vytvárajú streamy objektov a komprimované krížové odkazy, je popísaná v našom článku o streamoch objektov a inkrementálnych aktualizáciách. Keď je príslušný hybridný súbor zároveň veľmi veľký, techniky načítania v návode na Direct File API pre prácu s veľkými PDF vám umožnia skontrolovať ho bez čítania celého súboru do pamäte. Obe možnosti sa prirodzene spájajú s obnovou popísanou tu, ktorá sa dodáva ako súčasť HotPDF Component pre Delphi a C++Builder spolu s API na načítanie, úpravu, šifrovanie a podpisovanie popísanými inde na tomto blogu.