Deschideți un PDF pe care l-a produs Microsoft Word sau Excel, navigați prin el și nimic nu pare neobișnuit. Încărcați-l într-un program Delphi, citiți numărul de pagini, iar numărul este corect. Apoi salvați-l din nou cu criptarea activată, iar operațiunea eșuează cu o eroare EListError sau rezultatul se deschide cu un avertisment de referință încrucișată deteriorată. Fișierul nu a fost niciodată corupt. Este un fișier cu referințe hibride, iar însăși structura care permite unui vizualizator vechi de cincisprezece ani să-l deschidă este structura care învinge un încărcător care se oprește din citit prea devreme.
Aceasta este una dintre cele mai frecvente modalități prin care o conductă PDF care a trecut toate testele interne întâlnește un fișier pe care nu îl poate procesa complet. Datele de intrare au fost toate generate intern, deci nu au fost niciodată hibride. Primul fișier hibrid sosește în ziua în care un client trimite o factură exportată dintr-o foaie de calcul.
Ce scriu de fapt Word și Excel
ISO 32000-1 descrie aspectul referințelor hibride în §7.5.8.4. O aplicație care dorește funcționalități PDF 1.5, cum ar fi fluxurile de obiecte, permițând în același timp unui cititor PDF 1.4 să deschidă fișierul, scrie informațiile de referință încrucișată de două ori. Există un tabel clasic de referințe încrucișate, rândurile ASCII cu lățime fixă care încheiau fiecare PDF până la versiunea 1.4, și există un flux de referințe încrucișate care indexează restul. Secțiunea finală (trailer) a secțiunii clasice conține o intrare /XRefStm a cărei valoare este decalajul în octeți (offset) al acelui flux.
Împărțirea sarcinilor este deliberată. Obiectele la care trebuie să ajungă un cititor vechi, printre care catalogul și arborele de pagini, sunt adresabile din tabelul clasic. Obiectele care au fost pliate în fluxuri de obiecte comprimate sunt marcate ca libere în tabelul clasic, cu o intrare de tip f, astfel încât un cititor 1.4 trece direct peste ele și nu se lovește niciodată de o structură pe care nu o poate analiza. Locațiile lor reale se află doar în fluxul de referințe încrucișate. Semnătura unui astfel de fișier este finalul său: o scurtă secțiune clasică, adesea nimic mai mult decât xref urmat de un antet de subsecțiune 0 0, al cărui trailer indică spre /XRefStm unde se află datele reale de recuperare.
De ce un număr corect de pagini nu dovedește nimic
Deoarece catalogul și arborele de pagini sunt accesibile din tabelul clasic în mod intenționat, un încărcător care citește doar acel tabel găsește /Root, parcurge arborele de pagini și raportează numărul corect de pagini. Tot ceea ce are nevoie un cititor vechi este prezent, așa că fișierul pare intact. Obiectele care au dispărut sunt cele împachetate în fluxuri de obiecte: dicționare de câmpuri AcroForm, elemente de structură PDF etichetate, șirul lung de dicționare mici care nu a trebuit niciodată să fie vizibile pentru un vizualizator vechi.
Nu observați decalajul până când ceva nu atinge acele obiecte, iar o salvare completă le atinge pe toate. Parcurgerea documentului pentru a-l re-cripta sau re-scrie este tocmai operațiunea care solicită fiecare număr de obiect pe rând, motiv pentru care simptomul apare la momentul salvării și nu la cel al încărcării, departe de cauza sa.
Capcana este un detector care vede xref și se oprește
Calea simplă de a decide cum este indexat un fișier este să urmăriți startxref și să inspectați primii octeți către care indică. Cuvântul cheie xref înseamnă un tabel clasic; un obiect de tip flux înseamnă un flux de referințe încrucișate. Acest test este corect pentru orice fișier care folosește o singură schemă. Este greșit pentru un fișier hibrid, al cărui startxref vizează o secțiune clasică cu unicul scop de a satisface cititorii vechi, în timp ce intrarea /XRefStm din trailerul acelei secțiuni este locul unde este indexat de fapt majoritatea documentului. Un detector care returnează „classic” la primul xref pe care îl întâlnește nu citește niciodată /XRefStm, iar fiecare obiect care trăiește doar în flux devine invizibil.
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;
Cu detectorul de ieșire timpurie activat, încărcarea pare în regulă, iar salvarea este momentul în care obiectele absente își fac simțită prezența. Remedierea nu constă în citirea mai multor octeți la început; constă în recunoașterea trailerului hibrid și urmărirea /XRefStm înainte de a decide că analiza fișierului s-a încheiat.
Ordinea de îmbinare nu este negociabilă
Odată ce ambele indexuri au fost citite, ele pot fi combinate într-o singură direcție. Fluxul de referințe încrucișate trebuie îmbinat mai întâi, iar intrările clasice completate în jurul lui. Motivul este mica înșelătorie din centrul formatului. Un fișier hibrid își marchează obiectele comprimate ca fiind libere în tabelul clasic, astfel încât cititorii vechi să le ignore. Un încărcător care respectă o politică de tip „prima apariție câștigă” și citește mai întâi tabelul clasic va înregistra acele numere de obiecte ca libere, apoi va elimina intrările de flux care le localizează de fapt, deoarece sloturile sunt deja ocupate. Inversați ordinea și intrările de tip 2 din flux, fiecare reprezentând un număr de flux de obiecte plus un index, vor câștiga sloturile pe care ar trebui să le dețină, iar intrările clasice se vor așeza în jurul lor.
Aceeași disciplină previne ca o revizuire mai veche să reînvie un obiect șters. Actualizările incrementale se conectează înapoi prin /Prev, iar o intrare liberă de tip 0 este o santinelă care indică faptul că o secțiune mai recentă a retras un număr de obiect. O secțiune ulterioară, mai veche din lanț nu trebuie să aibă permisiunea de a suprascrie acea santinelă cu o locație învechită. Tratați prima apariție ca autoritară pentru marcajele libere și obiectul șters rămâne șters; tratați-l cu neglijență și propria istorie a fișierului va reanima conținutul pe care ultima revizuire l-a eliminat.
Ce înseamnă acest lucru în HotPDF
Motorul rezolvă fișierele cu referințe hibride în locul dvs. și face acest lucru pe fiecare cale care trebuie să analizeze datele de referință încrucișată. Încărcați un document cu LoadFromFile sau LoadFromStream, efectuați modificările și apelați SaveLoadedDocument; sau rulați o operațiune directă, cum ar fi EncryptFile, care citește o intrare și scrie o ieșire. În ambele cazuri, recuperarea citește /XRefStm, îmbină secțiunea de flux înaintea intrărilor clasice și rezolvă obiectele care trăiesc în fluxuri înainte ca scrierea să le enumere. Calea de criptare AES-256 este locul în care problema s-a manifestat prima dată, deoarece criptarea unui document rescrie fiecare obiect și, prin urmare, cere ca fiecare obiect să fi fost deja localizat.
// 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]);
Detaliul care merită reținut se află în amonte de API. Fișierele care provin din Word, Excel, PowerPoint și dintr-o listă lungă de fluxuri „Save as PDF” sunt în mod obișnuit hibride, așa că este posibil ca un încărcător pe care îl testați doar cu rezultatele propriului generator să nu întâlnească niciodată un astfel de fișier în timpul testării. Adăugați în seturile de testare documente exportate din aplicații Office reale, nu doar fișiere pe care le-a produs propriul cod.
Verificarea unui fișier suspect
Două inspecții clarifică rapid problema. Deschideți fișierul într-o vizualizare hexazecimală și citiți octeții de după ultimul startxref; un fișier hibrid arată o scurtă secțiune clasică a cărei secțiune trailer conține /XRefStm. Sau comparați numărul de obiecte raportat de o analiză completă cu cel mai mare număr de obiect pe care /Size îl declară în trailer. O diferență mare înseamnă că obiectele se ascund în fluxuri pe care încărcătorul nu le-a deschis, ceea ce reprezintă același deficit care se transformă ulterior într-un eșec la salvare.
Partea de scriere a acestei povești, modul în care sunt produse în primul rând fluxurile de obiecte și referințele încrucișate comprimate, este acoperită în articolul nostru despre fluxuri de obiecte și actualizări incrementale. Atunci când fișierul hibrid în cauză este de asemenea foarte mare, tehnicile de încărcare din ghidul API Direct File pentru fluxuri de lucru PDF mari vă permit să-l inspectați fără a citi totul în memorie. Ambele se corelează natural cu recuperarea descrisă aici, care este livrată ca parte a HotPDF Component pentru Delphi și C++Builder, alături de API-urile de încărcare, editare, criptare și semnare acoperite în alte părți ale acestui blog.