Technical Article

Hibrid hivatkozású PDF-ek betöltése Wordből és Excelből Delphi alatt

Nyisson meg egy Microsoft Word vagy Excel által készített PDF-et, lapozzon bele, és semmi sem tűnik szokatlannak. Töltse be egy Delphi programba, olvassa le az oldalszámot, és a szám helyes. Ezután mentse el újra bekapcsolt titkosítással, és a folyamat EListError hibával meghiúsul, vagy a kimenet sérült kereszthivatkozás-figyelmeztetéssel nyílik meg. A fájl soha nem volt sérült. Ez egy hibrid hivatkozású fájl, és pontosan az a struktúra, amely lehetővé teszi egy tizenöt éves megjelenítőnek a megnyitását, győzi le az olyan betöltőt, amely túl korán abbahagyja az olvasást.

Ez az egyik leggyakoribb módja annak, hogy egy minden belső teszten átment PDF-feldolgozó lánc olyan fájllal találkozik, amelyet nem képes újra elmenteni. A bemenetek mind házon belül generáltak voltak, így soha nem voltak hibridek. Az első hibrid fájl azon a napon érkezik meg, amikor az ügyfél továbbít egy táblázatból exportált számlát.

Mit ír valójában a Word és az Excel

Az ISO 32000-1 a 7.5.8.4. szakaszban írja le a hibrid hivatkozású elrendezést. Egy alkalmazás, amely szeretné használni a PDF 1.5 funkcióit (például az objektumfolyamokat), miközben továbbra is lehetővé teszi egy PDF 1.4-es olvasónak a fájl megnyitását, kétszer írja ki a kereszthivatkozási információkat. Létezik a klasszikus kereszthivatkozási tábla (a rögzített szélességű ASCII sorok, amelyek minden PDF-et lezártak az 1.4-es verzióig), és létezik egy kereszthivatkozási adatfolyam (cross-reference stream), amely a többi részt indexeli. A klasszikus szakasz lezárója (trailer) tartalmaz egy /XRefStm bejegyzést, amelynek értéke a folyam bájteltolása.

A feladatok megosztása szándékos. Azok az objektumok, amelyeket egy régi olvasónak el kell érnie (köztük a katalógus és az oldalfa), a klasszikus táblából címezhetők. A tömörített objektumfolyamokba (object streams) hajtogatott objektumok szabadnak vannak jelölve a klasszikus táblázatban egy f típusú bejegyzéssel, így az 1.4-es olvasó egyenesen átugrik rajtuk, és soha nem ütközik olyan struktúrába, amelyet nem tud értelmezni. A valós helyük kizárólag a kereszthivatkozási adatfolyamban él. Az ilyen fájlok jellemzője a végük: egy rövid klasszikus szakasz, gyakran semmi más, mint a xref, amelyet egy 0 0 alszakasz-fejléc követ, amelynek trailere arra a /XRefStm-re mutat, ahol a tényleges helyreállítási adatok találhatók.

Miért nem bizonyít semmit a helyes oldalszám

Mivel a katalógus és az oldalfa szándékosan elérhető a klasszikus táblából, az a betöltő, amely csak ezt a táblát olvassa be, megtalálja a /Root-ot, bejárja az oldalfát, és a helyes oldalszámot jelenti. Minden megvan, amire egy régi olvasónak szüksége van, így a fájl egészségesnek tűnik. A hiányzó objektumok azok, amelyeket objektumfolyamokba csomagoltak: AcroForm mező-szótárak, címkézett PDF (tagged-PDF) struktúraelemek, valamint a kis szótárak hosszú sora, amelyeknek soha nem kellett láthatóvá válniuk egy örökölt megjelenítő számára.

A hiányt egészen addig nem veszi észre, amíg valami hozzá nem ér ezekhez az objektumokhoz, és a teljes újra-mentés mindegyiküket érinti. A dokumentum bejárása az újra-titkosításhoz vagy újra-íráshoz pontosan az a művelet, amely sorban lekéri az összes objektumszámot, ezért jelentkezik a tünet a mentéskor, és nem a betöltéskor, messze a tényleges okától.

A csapda az a detektor, amely látja az xrefet és leáll

Az olcsó módja annak eldöntésének, hogyan van indexelve egy fájl, a startxref követése és az első bájtok megvizsgálása, amelyekre mutat. A xref kulcsszó klasszikus táblát jelent; a stream objektum kereszthivatkozási adatfolyamot jelent. Ez a teszt helyes minden olyan fájl esetében, amely elkötelezi magát egy sémára. Helytelen viszont egy hibrid fájlnál, amelynek startxref-je egy klasszikus szakaszra irányul pusztán azért, hogy kielégítse a régi olvasókat, miközben a szakasz trailerében lévő /XRefStm az a hely, ahol a dokumentum nagy része valójában indexelve van. Az a detektor, amely "klasszikus" értéket ad vissza az első xref találatnál, soha nem olvassa be a /XRefStm-et, és minden objektum láthatatlanná válik, amely csak a folyamban létezik.

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;

A korai leállást végző detektorral a betöltés jól néz ki, és az újra-mentés az ahol a hiányzó objektumok jelzik magukat. A javítás nem az, hogy több bájtot olvasunk be az elején; hanem az, hogy felismerjük a hibrid trailert, és követjük a /XRefStm-et, mielőtt eldöntenénk, hogy a fájl feldolgozása kész.

Az összefésülési sorrend nem vitatható

Miután mindkét indexet beolvastuk, csak egyetlen irányban lehet őket kombinálni. A kereszthivatkozási adatfolyamot kell először összefésülni, a klasszikus bejegyzéseket pedig e köré kell beírni. Ennek oka a formátum mélyén lévő kis megtévesztés. Egy hibrid fájl szabadnak jelöli meg tömörített objektumait a klasszikus táblázatban, hogy a régi olvasók figyelmen kívül hagyják őket. Az a betöltő, amely a "first-seen-wins" (elsőként látott győz) elvet követi, és először a klasszikus táblát olvassa be, szabadként fogja regisztrálni ezeket az objektumszámokat, majd elveti a folyam bejegyzéseit, amelyek ténylegesen megtalálnák őket, mivel a helyek már foglaltak. Fordítsa meg a sorrendet, és a folyamból származó 2-es típusú bejegyzések – mindegyik egy objektumfolyam-szám plusz egy index – elnyerik a nekik szánt helyeket, és a klasszikus bejegyzések elrendeződnek köréjük.

Ugyanaz a fegyelem véd meg attól, hogy egy korábbi verzió feltámasszon egy törölt objektumot. Az inkrementális frissítések visszafelé láncolódnak a /Prev segítségével, és a 0-s típusú szabad bejegyzés egy jelzőőr (sentinel), amellyel egy újabb szakasz nyugdíjazta az objektumszámot. A láncban később álló, régebbi szakasznak nem szabad megengedni, hogy felülírja ezt a jelzőőrt egy elavult helyszínnel. Kezelje az elsőként látottat mérvadóként a szabad jelölőknél, és a törölt objektum törölve marad; kezelje figyelmetlenül, és a fájl saját története újraéleszti azt a tartalmat, amelyet a legfrissebb változat eltávolított.

Mit jelent ez a HotPDF-ben

A motor feloldja Ön helyett a hibrid hivatkozású fájlokat, és ezt megteszi minden olyan útvonalon, amelynek elemeznie kell a kereszthivatkozási adatokat. Töltse be egy dokumentumot a LoadFromFile vagy LoadFromStream segítségével, hajtsa végre a változtatásokat, majd hívja meg a SaveLoadedDocument-et; vagy futtasson egy egylépéses műveletet, például a EncryptFile-t, amely beolvas egy bemenetet és kiír egy kimenetet. A helyreállítás mindenképpen beolvassa az /XRefStm-et, összefésüli az adatfolyam-szakaszt a klasszikus bejegyzések előtt, és feloldja a folyamokban élő objektumokat, mielőtt az írás felsorolná őket. Az AES-256 titkosítási útvonal volt az, ahol a probléma először megmutatkozott, mert a dokumentum titkosítása minden objektumot újraír, és így megköveteli, hogy minden objektum előzetesen meg legyen találva.

// 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]);

Az API felett található részletet érdemek megjegyezni. A Word, Excel, PowerPoint alkalmazásokból és a "Mentés másként PDF" folyamatok hosszú sorából érkező fájlok rutinszerűen hibridek, így a betöltő, amelyet csak a saját generátorának kimenetével tesztel, a tesztelés során soha nem találkozik ilyennel. Építsen be a tesztkörnyezetébe valódi Office alkalmazásokból exportált dokumentumokat is, ne csak a saját kódja által előállított fájlokat.

Gyanús fájl ellenőrzése

Két vizsgálat gyorsan eldönti a kérdést. Nyissa meg a fájlt egy hexadecimális nézetben, és olvassa le a bájtokat az utolsó startxref után; a hibrid fájl egy rövid klasszikus szakaszt mutat, amelynek trailer szótára tartalmazza a /XRefStm-et. Vagy hasonlítsa össze a teljes elemzés által jelentett objektumszámot a trailerben a /Size által megadott legmagasabb objektumszámmal. A nagy eltérés azt jelenti, hogy az objektumok olyan folyamokban rejtőznek, amelyeket a betöltő nem nyitott meg, ami ugyanaz a hiányosság, amely később a mentéskori hibához vezet.

Ennek a történetnek az írói oldala, vagyis az, hogyan jönnek létre az objektumfolyamok és a tömörített kereszthivatkozások, az objektumfolyamokról és inkrementális frissítésekről szóló cikkünkben olvasható. Ha a kérdéses hibrid fájl egyben nagyon nagy is, a Direct File API útmutató a nagy PDF munkafolyamatokhoz bemutatja a betöltési technikákat, amelyek segítségével anélkül vizsgálhatja meg, hogy az egészet beolvasná a memóriába. Mindkettő természetes módon illeszkedik az itt leírt helyreállításhoz, amely a Delphihez és C++Builderhez készült HotPDF Component része, a blogon máshol tárgyalt betöltési, szerkesztési, titkosítási és aláírási API-k mellett.