Odborný článok

Detekcia hybridných referenčných (hybrid-reference) PDF dokumentov z aplikácií Office vo vyhodnocovacích kanáloch Delphi

Ak exportujete dokument z programov Microsoft Word alebo Excel prostredníctvom Uložiť ako PDF, súbor na disku bude, s najväčšou pravdepodobnosťou, predstavovať typ súboru s hybridnými odkazmi (hybrid-reference). Tieto nesú informácie s krížovými odkazmi hneď na dvakrát: raz ako klasickú tabuľku s pevnou šírkou, akou každý takýto PDF končil až do verzie 1.4, a druhýkrát v podobe komprimovaného toku z krížových odkazov (cross-reference stream), od ktorého reálne závisí už samotná väčšina obsahu dokumentu. Iba jeden jediný kľúč pätovej časti (trailer key), /XRefStm, dokáže následne obidva tieto pohľady prešiť k sebe a zároveň to, či konkrétny nástroj v celosti reálne nahliada na dokument ako obrovský celok, už priamo závisí len na tom, či nasleduje alebo nie takto postavený kľúč

Tento článok si na hybridné súbory posvieti priamo od samotnej konzumnej strany: ako vyzerajú jednotlivé bajty úplne na konci súboru, ako sa tieto dva pohľady pri upravovaní dokážu od seba odpútať a zísť z cesty a ako dokáže procesný kanál z prostredia pre Delphi detegovať a prepájať vstupné hybridné súbory. To, akým spôsobom dokáže načítač zlúčiť (merges) oba tieto pohľady navzájom a tiež prečo ich poradie ani nie je predmetom na vyjednávanie, zostáva témou pre náš článok zo spoločnosti HotPDF venovaný práve načítavaniu takýchto súborov na úrovni hybrid-reference; tu v tomto konkrétnom článku sa totiž venujeme práve a primárne spoznávaniu tohto rozloženia pekne od základov

Prečo aplikácie Office v exportoch zapisujú register odkazov hneď dvakrát

Verzia PDF 1.5 priniesla dve funkcie, ktoré zmenili štruktúru súborov: toky krížových odkazov (cross-reference streams), ktoré ukladajú index objektov vo forme komprimovaných binárnych dát namiesto tabuľky v čistom texte, a objektové toky (object streams), ktoré dokážu zabaliť hneď niekoľko malých objektov do jedného Flate-komprimovaného kontajnera. Zapisovacie rozhranie, ktoré ich využíva, tak generuje menšie súbory, ale softvér na čítanie vo verzii PDF 1.4 nedokáže takýto výsledok otvoriť, keďže štruktúry, na ktoré sa kľúčuje a plne spolieha — kľúčové slovo xref a slovník pre pätu (trailer) — už nie sú k dispozícii

Norma ISO 32000-1 §7.5.8.4 však definuje v tomto určitý kompromis. Súbory z oblasti hybrid-reference totiž zapisujú hneď obe strany z rovnice: klasickú tabuľku s formátom krížových odkazov, adresujúcu presne tie objekty, na ktoré staršie generácie načítačov musia z postupov dosiahnuť (katalóg a strom stránok sú ukryté medzi nimi), a zároveň rovnako aj priradený tok z krížových odkazov (cross-reference stream), ktorý indexuje všetko ostatné. Objekty schované (folded) a uplatnené do objektových tokov sú v klasickej tabuľke označené ako voľné (marked free in the classic table), a staršie čítačky s omedzeným prístupom z verzie 1.4 ich tak prebehnú a preskočia plynulo celkom bez akýchkoľvek sťažností; ich reálne umiestnenia totiž existujú naozaj už iba priamo v toku (exist only in the stream). Klasická päta na konci v sebe ďalej nesie aj príslušný kľúč /XRefStm s podržaným bajtovým odsadením patriacim práve pre samotný tok. Starý prehliadač nikdy kľúč neprečíta a súbor tak vykresľuje z pohľadu pre tabuľku. Moderný prehliadač na to naopak nadviaže a tak vidí úplne a priamo celý kompletný dokument. Presne tento konkrétny model z usporiadaného formátu uplatňovali pre export z Wordu a Excelu už celú radu rokov, z akého dôvodu nepredstavujú hybridné súbory ani zďaleka žiaden exotický hraničný prípad pre menšiny, ale naopak masívny balík uplatnení s akými sa bežné obchodné procesné linky pre nasadenie každodenne stretávajú

Ako vyzerá pätová koncová časť vo formátoch hybridných súborov

Samotné rozloženie sa totiž dá najjednoduchšie pochopiť priamo na rovine bajtov (easiest to understand from the bytes). Tu v ukážke je zobrazená koncová päta pochádzajúca z malého hybridného súboru na ktorej boli odsadenia cielene skrátené; vo formátoch v reálnych aplikáciách Office z ich exportu tvorí u prislúchajúcich hodnota /XRefStm pre kľúč k uplatneniu spravidla pomerne ohromné posunutie pre odsadenie uložené takmer úplne z blízkeho na konci súboru. S u k (The reading order is the tail-first walk described in our overview of PDF file structure: find %%EOF, read startxref, jump to the table)

% ... body objects, including object streams and, at byte 116,
% the cross-reference stream (a stream object with /Type /XRef) ...

xref                    % classic section: what startxref points at
0 4
0000000000 65535 f      % slot 0: head of the free list, always present
0000000017 00000 n      % object 1: the catalog, visible to any reader
0000000000 65535 f      % object 2: marked free -- lives in an object stream
0000000000 65535 f      % object 3: same; only the stream view locates it
trailer
<<
  /Size 4
  /Root 1 0 R
  /XRefStm 116          % byte offset of the cross-reference stream
>>
startxref
7164                    % byte offset of the 'xref' keyword above
%%EOF

Dva detaily z tohto výpisu objasňujú fungovanie celého mechanizmu. Po prvé, startxref mieri priamo na klasickú časť, a to úplne úmyselne: toto je totiž tá presná adresa, na ktorej musí stará čítačka pristáť. Prúd krížových odkazov je naopak dosiahnuteľný len cez kľúč /XRefStm ukrytý vnútri v slovníku pre pätu (trailer dictionary), a tak parser, ktorý takýto kľúč nikdy nehľadá, sa nikdy nedozvie, že by takýto prúd vlastne vôbec existoval. Po druhé, objekty 2 a 3 klamú, no slúži to k dobrému. Klasická tabuľka ich síce deklaruje ako voľné, ide ale o skutočné objekty sediace vo vnútri komprimovaného kontajnera; toto voľné označenie drží verziu pre 1.4 v bezpečí pred zakopnutím a obsluhou u záznamov, ktoré jednoducho nedokáže nijako využiť. Spotrebiteľ veriaci iba klasickému zverejnenému pohľadu v domnienkach tak usúdi, že väčšina zverejneného dokumentu jednoducho vôbec neexistuje

Ako sa dokážu oba pohľady od seba odchýliť

Nový hybridný súbor pochádzajúci priamo z prostredia Word je vnútorne plne konzistentný: oba dva pohľady rovnako opisujú úplne ten istý rovnaký dokument, každý v rámci svojho rozsahu. Problémy sa ale začínajú pri úprave súboru s programom, ktorý rozumie vždy iba jednému z týchto pohľadov. Vezmite do úvahy pečiatkovací program, ktorý k súboru pripája prírastkovú inkrementálnu aktualizáciu: nové objekty, novú xref sekciu, novú reťaz a cestu v /Prev k predošlej časti a celkom novú pätu v trailer. Pokiaľ takáto nová ohraničená päta odhodí kľúč /XRefStm, prúd s pohľadom osirie; pokiaľ si ale starú hodnotu bez vedomia skopíruje, prúd s pohľadom opäť len popisuje a uplatňuje to, kde bol dokument opísaný už pred samotnou zmenou. Nech sa to uplatní hocijako, oba indexy sa nedokážu zhodnúť a nesúhlasia navzájom ohľadom toho, čo súbor a celok v skutočnosti obsahuje

Výsledný takýto dokument má pri čítaní chybný podpis: objekty viditeľné v jednom pohľade chýbajú v druhom. Čítačka rozuzľujúca postupy zo stránky prúdu nájde pôvodne neupravenú verziu objektu, alebo vôbec nenájde priložený pridaný objekt. Čítačka využívajúca pohľad pre tabuľku síce modifikáciu vidí, no stráca stopu pri komprimovaných objektoch, ktoré vie prúd presne umiestniť

Čo robí s takýmito súbormi pri chybách také zložité je, že Acrobat ich vo väčšine prípadov otvorí bez pripomienok: ak sa index nezhoduje s bajtami, potichu prebuduje krížové údaje skenovaním objektových hlavičiek, takže ten, kto rozbitý súbor vyrobil, nevidí nič zlé. Chyba sa vynorí až neskôr, keď súbor narazí na prísnejšieho konzumenta, preflight kontrolu, podpisovú službu alebo archivačný systém, ktorý dôveruje deklarovanej štruktúre a podáva správu o chýbajúcich objektoch alebo o nezhode v krížových odkazoch. "Otvorí sa to v Acrobate" je veta, ktorou začína takmer každá chyba pri synchronizácii hybridných PDF

Detekcia hybridného súboru v čistom kódovom Delphi (plain Delphi)

Rozdeľovanie nevyžaduje od obmedzení knižnicu PDF. Kľúč /XRefStm je nájdený len v klasickej pätovej časti v slovníku, a aktívna päta leží pár kilobajtov pred koncom súboru, pretože špecifikácia vyžaduje, aby %%EOF bol blízko fyzického konca. Prečítanie obmedzeného okna päty a jej vyhľadávanie je pre triedenie dostačujúce:

uses
  System.SysUtils, System.Classes, System.StrUtils, System.Math;

function IsHybridReferencePdf(const FileName: string): Boolean;
const
  TailWindow = 2048;
var
  Stream: TFileStream;
  Buf: TBytes;
  Tail: string;
  Len, TrailerPos, NextPos, KeyPos, StartXrefPos: Integer;
begin
  Result := False;
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    if Stream.Size < 48 then
      Exit;
    Len := Min(TailWindow, Integer(Stream.Size));
    SetLength(Buf, Len);
    Stream.Position := Stream.Size - Len;
    Stream.ReadBuffer(Buf[0], Len);
  finally
    Stream.Free;
  end;

  // Every keyword involved is 7-bit ASCII, so a byte-wise decode is safe
  Tail := TEncoding.ANSI.GetString(Buf);

  // Find the LAST 'trailer' keyword: with incremental updates,
  // the newest trailer is the one that governs the file
  TrailerPos := 0;
  NextPos := Pos('trailer', Tail);
  while NextPos > 0 do
  begin
    TrailerPos := NextPos;
    NextPos := PosEx('trailer', Tail, NextPos + 1);
  end;
  if TrailerPos = 0 then
    Exit;  // no classic trailer: a pure xref-stream file, not hybrid

  // A hybrid trailer carries /XRefStm between 'trailer' and 'startxref'
  KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
  StartXrefPos := PosEx('startxref', Tail, TrailerPos);
  Result := (KeyPos > 0) and
    ((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;

Uvedené tri výsledky priamo korelujú s tromi rozloženiami. Čisto klasický súbor má pätu, ale nemá kľúč /XRefStm: hodnota False. Súbor, ktorý plne prechádza na toky krížových odkazov, vôbec nemá kľúčové slovo trailer, jeho kľúče pre pätu žijú priamo v slovníku prúdu (stream dictionary): tiež False, a to správne, pretože takýto súbor je komprimovaný, nie hybridný. Iba rozloženie s dvojitým indexom (double-indexed layout) vráti hodnotu True

Pre produkčné nasadenie stoja dve bezpečnostné zosilnenia (hardenings) za tie riadky navyše. Analyzujte celé číslo nachádzajúce sa za /XRefStm, prejdite na tento príslušný offset a tam sa uistite, že objekt s prúdom typu /Type /XRef tam skutočne sedí; orezaný poškodený súbor môže kľúč naďalej niesť aj keď samotný prúd bol odstránený, čo samozrejme patrí do iného chybového koša ako len nejaký zdravý hybrid. K veľkosti vyhľadávacieho okna pristupujte ako k parametru: 2 KB síce pokrýva bežné výstupy z balíka Office, ale nezvyčajne veľký slovník pre pätu môže odtlačiť kľúčové slovo mimo náš rozsah a tak je zväčšenie okna pri takýchto postupoch lepšie a spoľahlivejšie než nechať systém omylom označiť súbor ako klasický

Smerovanie hybridných súborov cez vyhodnocovací kanál pre Delphi

Detekcia si kupuje vaše rozhodnutie o smerovaní. Pre súbory, ktoré sa len čítajú, vykresľujú, alebo kontrolujú, použite zavádzač, ktorý dokáže rozlúštiť oba dva pohľady, a v testoch sa následne spoliehajte na kontrolu správania sa súboru namiesto analýzy bajtov. Nástroj PDFium Component analyzuje celú túto väzbu od kľúča /XRefStm už v čase načítania, takže tabuľka objektov tak ako ju vidí váš kód predstavuje tento už zlúčený a spracovaný celok, a kontroly podrobne opísané v našom článku o validácií objektov a tokov krížových odkazov tu platia nezmenené. Ak je takýto desynchronizovaný hybrid poškodený natoľko, aby načítavanie priamo odmietol, engine to ohlási pomocou vlastného setu chýb: FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY a FPDF_ERR_PAGE, pričom za produkciu pri štrukturálnom poškodení zodpovedá priamo FPDF_ERR_FORMAT. Nespoliehajte sa však na tento signál: jadro PDFium je z návrhu stavané tolerantne a aj väčšinu takto poškodených nesúvislých súborov prebuduje v tichosti, úspešné načítanie tak naisto preukazuje jedine to, že súbor sa dokázal zrekonštruovať, no nie to, že by obidva jeho pohľady súhlasili. Skutočným overovaním zmysluplnej konzistencie je naďalej porovnávanie toho, čo nájde úplný prechod cez objekty, oproti tomu, čo deklaruje hodnota /Size v päte

Pre tie súbory, ktoré vaša procesná rúra (pipeline) modifikuje, tým úplne najbezpečnejším zásadným postupom bude zastaviť ich hybridnosť úplne. Načítanie s okamžitým následným úplným uložením prostredníctvom nástroja HotPDF (full save through HotPDF) prepíše pôvodný dokument za využitia samostatných a plne samo-konzistentných krížových odkazov v jedinej forme: žiadne /XRefStm, žiaden druhý pohľad spôsobilý vypadnúť zo synchronizácie, každý jeden samostatný objekt je presne výhradne riadený len jedným jediným záznamom v indexe. Takáto normalizácia je presne tým krokom, aký potrebujete pri postupoch pred archiváciou (before archival ingest), hneď pred striktným downstream RIP alebo podpisovou službou, a po akejkoľvek editácii aplikovanej na hybridný vstup. Tento postup funguje, pretože zavádzač zlúčil pohľady správne už pri vstupe, presne podľa mechanizmu, ktorý detailne rozoberá článok o hybridných odkazoch v HotPDF

Jedinou triedou súborov, ktorú by ste mali nechať na pokoji, sú digitálne podpísané dokumenty. Kompletný prepis presunie každý bajt, čo ruší platnosť akéhokoľvek podpisu vypočítaného z pôvodných rozsahov. Úprava podpísaného hybridného dokumentu musí ísť cez správnu inkrementálnu aktualizáciu, ktorá udržiava oba pohľady; súbor určený len na čítanie by mal prejsť nedotknutý. Normalizácia je určená pre súbory, ktoré vlastníte; k podpísaným súborom môžete len pripájať (append)

Hybridné referenčné PDF dokumenty nie sú poškodené; sú vlastným mostom kompatibility tohto formátu a aplikácie Office ich budú produkovať tak dlho, kým čítačky PDF 1.4 prežijú v inštalačnej základni. Vyhodnocovací kanál, ktorý dokáže spozorovať kľúč /XRefStm, validovať zlúčený dokument pomocou PDFium Component a zregenerovať čistý výstup s jedným indexom pomocou HotPDF Component, s nimi zaobchádza presne takými, aké reálne sú: obyčajné vstupy s jedným extra smerovníkom v päte