Nyisson meg egy PDF-et, amelyet a Microsoft Word vagy Excel állított elő, lapozzon végig rajta, és semmi sem tűnik szokatlannak. Töltse be egy Delphi programba, olvassa vissza az oldalszámot, és a szám helyes. Ezután mentse el újra bekapcsolt titkosítással, és a feladat meghiúsul egy EListError hibával, vagy a kimenet egy sérült kereszthivatkozás figyelmeztetéssel nyílik meg. A fájl sosem volt sérült. Ez egy hibrid hivatkozású fájl, és pont az a szerkezet, amely lehetővé teszi, hogy egy tizenöt éves megjelenítő megnyissa, az a szerkezet, amely legyőzi azt a betöltőt, amely túl korán hagyja abba az olvasást
Ez az egyik leggyakoribb módja annak, hogy egy PDF-folyamat, amely minden belső teszten átment, olyan fájllal találkozzon, amelyet nem tud oda-vissza átvinni. A bemeneteket mind házon belül generálták, így soha nem voltak hibridek. Az első hibrid fájl azon a napon érkezik, amikor az ügyfél továbbít egy táblázatból exportált számlát
Mit is í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 a PDF 1.5 funkcióit, például objektumfolyamokat szeretne, miközben továbbra is lehetővé teszi, hogy egy PDF 1.4 olvasó megnyissa a fájlt, kétszer írja be a kereszthivatkozási információkat. Van egy klasszikus kereszthivatkozási tábla, a fix szélességű ASCII sorok, amelyek minden PDF-et lezártak az 1.4-es verzióig, és van egy kereszthivatkozási folyam, amely indexeli a többit. A klasszikus szakasz végeleme egy /XRefStm bejegyzést hordoz, amelynek értéke a folyam bájteltolása
A munkamegosztás szándékos. Azok az objektumok, amelyeket egy régi olvasónak el kell érnie, beleértve a katalógust és az oldalfát, címezhetők a klasszikus táblából. Azok az objektumok, amelyeket tömörített objektumfolyamokba hajtottak össze, szabadként vannak megjelölve a klasszikus táblázatban, egy f típusú bejegyzéssel, így egy 1.4-es olvasó egyenesen átlépi őket, és soha nem botlik bele olyan szerkezetbe, amelyet nem tud elemezni. Valódi helyük csak a kereszthivatkozási folyamban található meg. Egy ilyen fájl aláírása a vége: egy rövid klasszikus szakasz, gyakran nem más, mint egy xref, amelyet egy 0 0 alszakasz fejléc követ, amelynek a végeleme 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ázatból, egy olyan betöltő, amely csak ezt a táblázatot olvassa, megtalálja a /Root-ot, végigjárja az oldalfát, és jelenti a megfelelő oldalszámot. Minden jelen van, 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 struktúraelemek, azon kis szótárak hosszú sora, amelyeknek soha nem kellett láthatónak lenniük egy régi megjelenítő számára
Ezt a hiányosságot addig nem veszi észre, amíg valami nem érinti ezeket az objektumokat, és egy teljes újramentés mindet érinti. A dokumentum végigjárása az újratitkosításhoz vagy az újraíráshoz pontosan az a művelet, amely minden objektumszámot sorban bekér, és ez az oka annak, hogy a tünet a mentéskor jelentkezik, nem pedig a betöltéskor, távol az okától
A csapda egy detektor, amely látja az xref-et és megáll
A legolcsóbb módja annak eldöntésére, hogy egy fájl hogyan van indexelve, az a startxref követése, és az első bájtok megvizsgálása, amelyekre mutat. A xref kulcsszó egy klasszikus táblát jelent; egy folyamobjektum egy kereszthivatkozási folyamot. Ez a teszt minden olyan fájl esetében helyes, amely elkötelezi magát az egyik séma mellett. Ez azonban helytelen egy hibrid fájl esetében, amelynek a startxref-je egy klasszikus szakaszra irányul azzal az egyetlen céllal, hogy kielégítse a régi olvasókat, míg az ebben a szakaszban lévő végelem szótárban található /XRefStm az, ahol a dokumentum nagy része valójában indexelve van. Egy detektor, amely "klasszikus"-t ad vissza az első xref-nél, amellyel találkozik, soha nem olvassa a /XRefStm-et, és minden olyan objektum, amely csak a folyamban él, láthatatlanná válik
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 kilépés érzékelőjével a betöltés rendben lévőnek tűnik, és az újramentés az, ahol a hiányzó objektumok bejelentik magukat. A javítás nem az, hogy több bájtot olvasunk az elején; a javítás az, hogy felismerjük a hibrid végelemet, és követjük az /XRefStm-et, mielőtt úgy döntenénk, hogy a fájl kész
Az összevonási sorrend nem alku tárgya
Miután mindkét indexet beolvasták, csak egy irányba kombinálhatók. A kereszthivatkozási folyamot először össze kell vonni, a klasszikus bejegyzésekkel kitöltve azt. Az ok az a kis megtévesztés, amely a formátum szívében található. Egy hibrid fájl tömörített objektumait szabadként jelöli meg a klasszikus táblázatban, hogy a régi olvasók figyelmen kívül hagyják őket. Egy betöltő, amely tiszteletben tartja az "elsőként látott nyer" irányelvet, és először a klasszikus táblázatot olvassa el, ezeket az objektumszámokat szabadként rögzíti, majd eldobja a folyambejegyzéseket, amelyek valójában meghatározzák a helyüket, mert a helyek már foglaltak. Fordítsa meg a sorrendet, és a folyamból származó 2. típusú bejegyzések, mindegyik egy objektumfolyamszám és egy index, megnyerik azokat a helyeket, amelyeket birtokolniuk kell, és a klasszikus bejegyzések körülöttük helyezkednek el
Ugyanez a fegyelem véd meg egy régebbi revíziótól, amely feltámasztana egy törölt objektumot. A növekményes frissítések a /Prev-en keresztül visszafelé láncolódnak, és egy 0-s típusú szabad bejegyzés az az őrszem, amely jelzi, hogy egy újabb szakasz visszavont egy objektumszámot. A láncban lévő későbbi, régebbi szakasznak nem szabad megengedni, hogy felülírja ezt az őrszemet egy elavult hellyel. Kezelje az "elsőként látott"-at mérvadóként a szabad markerek esetében, és a törölt objektum törölt marad; kezelje óvatlanul, és a fájl saját története újraéleszti azt a tartalmat, amelyet a legutóbbi revízió eltávolított
Mit jelent ez a HotPDF-ben
A motor feloldja Önnek a hibrid hivatkozású fájlokat, és megteszi ezt minden olyan útvonalon, amelynek elemeznie kell a kereszthivatkozási adatokat. Töltsön be egy dokumentumot a LoadFromFile vagy a LoadFromStream segítségével, hajtsa végre a módosításokat, és hívja meg a SaveLoadedDocument-et; vagy futtasson egy egyszeri műveletet, például az EncryptFile-t, amely egy bemenetet olvas és egy kimenetet ír. A helyreállítás mindkét esetben beolvassa az /XRefStm-et, összevonja a folyamszakaszt 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 az, ahol a probléma először megmutatta magát, mert egy dokumentum titkosítása újraírja az összes objektumot, és így megköveteli, hogy minden objektum már megtalálható legyen
// 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]);
A részlet, amelyet érdemes magunkkal vinni, az API-tól felfelé helyezkedik el. A Word, Excel, PowerPoint és a "Mentés PDF-ként" folyamok hosszú sorából érkező fájlok rutinszerűen hibridek, így egy betöltő, amelyet csak a saját generátorának kimenetével gyakorol, soha nem biztos, hogy találkozik eggyel a tesztelés során. Töltse fel a tesztkészleteit valódi Office alkalmazásokból exportált dokumentumokkal, ne csak azokkal a fájlokkal, amelyeket a saját kódja állított elő
Gyanús fájl ellenőrzése
Két ellenőrzés gyorsan eldönti a kérdést. Nyissa meg a fájlt egy hexadecimális nézetben, és olvassa el a végső startxref utáni bájtokat; egy hibrid fájl egy rövid klasszikus szakaszt mutat, amelynek végelem szótára tartalmazza az /XRefStm-et. Vagy hasonlítsa össze a teljes elemzés által jelentett objektumszámot a legmagasabb objektumszámmal, amelyet a /Size deklarál a végelemben. A nagy rés azt jelenti, hogy az objektumok olyan folyamokban bújnak meg, amelyeket a betöltő nem nyitott meg, ami ugyanaz a hiány, amely később mentéskori hibává válik
A tipikus Excel-export farka konkréttá teszi az első ellenőrzést. A végső xref kulcsszó után minden egyszerű ASCII, így az aláírás egyenesen egy hexadecimális nézetből olvasható (az eltolások illusztrációk, a magyarázatok hozzáadva)
xref
0 0 % empty classic subsection: no rows at all
trailer
<< /Size 216 % one past the highest object number in use
/Root 1 0 R
/Info 15 0 R
/ID [<5C9A...> <5C9A...>]
/XRefStm 87325 % byte offset of the cross-reference stream
>>
startxref
88710 % points at the classic section above
%%EOF
A 0 0 alszakasz az árulkodó jel: egy nulla bejegyzést tartalmazó klasszikus tábla csak azért létezik, hogy hordozza a végelemet, és a végelem főként azért létezik, hogy az /XRefStm 87325-öt mondja. Egy detektor, amely az xref kulcsszónál megáll, ezen a ponton a semmi indexét látta. Amikor inkább szkriptelni szeretné az ellenőrzést, mintsem szemrevételezni, a marker mindig a fájl utolsó pár kilobájtján belül található, így elegendő egy korlátozott visszafelé történő olvasás
// Returns the /XRefStm offset from the file's tail, or -1 if the
// marker is absent (the file is not hybrid, or not a PDF at all)
function FindXRefStm(const FileName: string): Int64;
var
FS: TFileStream;
Tail: AnsiString;
Len, P: Integer;
begin
Result := -1;
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Len := 2048; // the trailer lives in the tail
if FS.Size < Len then
Len := Integer(FS.Size);
FS.Position := FS.Size - Len; // bounded backward read: 2 KB max
SetLength(Tail, Len);
FS.ReadBuffer(Tail[1], Len);
finally
FS.Free;
end;
P := Pos(AnsiString('/XRefStm'), Tail);
if P = 0 then
Exit; // no hybrid marker in the tail
Inc(P, Length('/XRefStm'));
while (P <= Len) and (Tail[P] in [' ', #9, #13, #10]) do
Inc(P); // skip whitespace after the key
Result := 0;
while (P <= Len) and (Tail[P] in ['0'..'9']) do
begin
Result := Result * 10 + Ord(Tail[P]) - Ord('0');
Inc(P);
end;
end;
// Usage: a non-negative result names the byte where the stream starts
if FindXRefStm('Invoice_XLS.pdf') >= 0 then
Writeln('hybrid-reference file: resave will need the /XRefStm section');
Kezelje a szondát triázsként, ne elemzőként: megmondja, hogy a kötegből mely fájlok érdemelnek figyelmet egy újramentési feladat futtatása előtt, és semmi mást. Hogy mit kell tennie egy betöltőnek az általa talált eltolással, követve a szakaszláncot, összevonva a folyambejegyzéseket a klasszikusok előtt, tiszteletben tartva a szabad bejegyzésű őrszemeket, azt lépésről lépésre végigjárjuk a társcikkünkben, amely a hibrid hivatkozású PDF-ek Office-alkalmazásokból történő kezeléséről szól
Ennek a történetnek az írói oldala, hogyan jönnek létre elsősorban az objektumfolyamok és a tömörített kereszthivatkozások, az objektumfolyamokról és növekményes frissítésekről szóló cikkünkben olvasható. Ha a szóban forgó hibrid fájl is nagyon nagy, a Direct File API útmutató a nagy PDF-munkafolyamatokhoz című részben található betöltési technikák lehetővé teszik, hogy ellenőrizze anélkül, hogy az egészet beolvasná a memóriába. Mindkettő természetesen párosul az itt leírt helyreállítással, amely a Delphi és a C++Builder számára készült HotPDF Component részeként kerül forgalomba az ezen a blogon máshol bemutatott betöltő, szerkesztő, titkosító és aláíró API-k mellett