Technický článok

Skutočná redakcia PDF v Delphi: Vymazanie a sanitácia

Skutočná redakcia PDF v Delphi znamená vymazanie podkladových bajtov, nie ich pretretie. Knižnica losLab PDF Library kreslí túto čiaru dvoma rozhraniami API: RedactRegion upravuje tok obsahu stránky, takže odstránený text, vektory a obrázky sú fyzicky preč, a SanitizeDocument vymaže metadáta, skripty a akcie, v ktorých sa redigované informácie tiež ukrývajú. Ak urobíte chybu v ktorejkoľvek časti, odošlete súbor, ktorý na obrazovke vyzerá ako cenzurovaný, ale prezradí tajomstvo každému, kto skopíruje text alebo otvorí Vlastnosti dokumentu

Tento chybový stav je vecou verejnou. Noviny, súdy a vládne orgány už vydali PDF súbory s čiernym obdĺžnikom položeným cez meno alebo číslo, pričom čitateľovi stačilo vybrať text pod ním, skopírovať ho a vložiť údajne redigovaný reťazec priamo do zobrazenia. Obdĺžnik bol len kresbou. Text tam stále bol, nachádzal sa v toku obsahu jednu vrstvu pod farbou, plne neporušený a plne vybrateľný. losLab PDF Library, PDF engine pre Delphi a C++Builder vystavený prostredníctvom triedy TPDFlib, považuje tento rozdiel za samotnú podstatu redakcie a nie za dodatočný nápad

Prečo nie je nakreslenie čierneho boxu cez text v PDF skutočnou redakciou?

Knižnica losLab PDF Library vystavuje metódu MaskRect pre prípady, kedy je zakrytie presne to, čo chcete: vodoznak, vizuálne začiernenie, overovacia pečiatka. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) pripojí obdĺžnik k toku obsahu stránky a vykreslí ho pomocou operátora konštrukcie cesty re, po ktorom nasleduje operátor výplne f (ISO 32000-1 §8.5). Každý bajt, ktorý už na stránke bol — glyfy mena, ktoré ste zakryli, vektorové ťahy, vložený obrázok —, zostáva presne tam, kde bol, o jednu inštrukciu skôr v rovnakom toku. Kompatibilný prehliadač vykreslí čierny box ako posledný, takže vizuálne vyhrá. Zakrytie pod ním nič nemení

Ktokoľvek môže získať pôvodný obsah tromi spôsobmi: vybrať a skopírovať text priamo cez nepriehľadný box, spustiť extraktor textu na danej stránke alebo vymazať prekrývajúci obdĺžnik z toku obsahu. Metóda MaskRect je k tomu úprimná už vo svojom názve — maskuje, neodstraňuje. Výsledkom je len nálepka na slove, ktoré si stále môžete prečítať po odlepení nálepky

Ako RedactRegion vymazáva obsah na úrovni inštrukcií

Metóda RedactRegion(Page, Left, Top, Width, Height, Options) pracuje o jednu vrstvu pod farbou. losLab PDF Library analyzuje (parsuje) každú vrstvu obsahu stránky do TPDFContentProgram, prechádza zoznam inštrukcií s analyzátorom sledujúcim CTM (FindInstructionsInRect v modeli obsahu) a označí každý operátor zobrazujúci text, kresliaci cestu a operátor obrázka Do, ktorého vykreslená oblasť pretína váš obdĺžnik. Tieto inštrukcie sa odstránia pomocou TPDFContentProgram.Delete a upravená vrstva sa zapíše späť pomocou WritePageContentLayer. Bajty sa neposunú za kryt — opustia samotný tok. Textový extraktor spustený potom nenájde nič, pretože nie je čo nájsť

Dva detaily implementácie stoja za zváženie. RedactRegion prijíma obdĺžnik v aktuálnom počiatku a jednotkách (vľavo hore) a interne ho konvertuje na používateľský priestor vľavo dole, ktorý používa tok obsahu, takže oblasť zadávate tak, ako ju vidíte na stránke. Keď obdĺžnik pokrýva niekoľko inštrukcií, RedactRegion ich vymaže v zostupnom poradí indexov, pretože odstránenie inštrukcie 4 pred inštrukciou 7 by posunulo každý neskorší index a poškodilo rozsah. Ak odovzdáte Options = 0, losLab PDF Library tiež vytlačí plný čierny box cez vyčistenú oblasť ako viditeľnú značku — táto značka je však iba kozmetická, aplikovaná po tom, čo je skutočný obsah už preč

V praxi zriedka hardvérovo kódujete obdĺžnik. Stránku prehľadáte na výskyt citlivého reťazca a načítate jeho ohraničujúci rámček (bounding box) — rovnaké vyhľadávanie stránky a enumerácia prvkov, aké sú popísané v časti vyhľadávanie textu a prvkov stránky v Delphi — a potom tento obdĺžnik odovzdáte metóde RedactRegion:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Delete everything painted inside a 220 x 14 pt box on page 1.
      // Coordinates are top-left, in the current origin and units.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 stamps a black marker
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Kde sa v PDF stále skrývajú redigované informácie?

Redakcia, ktorá sa zastaví na viditeľnej vrstve, zanecháva stopu, pretože PDF ukladá informácie na miestach, kam sa čitateľ nikdy nepozrie. losLab PDF Library zoskupuje tieto úniky do kanálov, ktoré stoja za zmienku. Slovník informácií o dokumente (Document Information Dictionary) a tok metadát XMP bežne nesú reťazce Author (Autor), Producer (Producent) alebo Keywords (Kľúčové slová), ktoré pomenúvajú presne tú osobu alebo prípad, ktorý ste odstránili z tela dokumentu. JavaScript na úrovni dokumentu a katalógová akcia OpenAction sa môžu spustiť pri otvorení a získať prístup k údajom, o ktorých ste si mysleli, že sú preč. Každá anotácia nesie voliteľnú akciu /A a samotná anotácia — lepiaca poznámka, neaktuálne pole formulára, odkaz — môže sedieť priamo nad redigovaným miestom s vlastnou kópiou textu. Ničoho z toho sa šifrovanie nedotkne; šifrovaný súbor s nešifrovaným balíkom XMP stále prezrádza svoj názov akémukoľvek indexeru, čo je medzera, ktorá si sama osebe vyžaduje auditovanie šifrovania a oprávnení. Obhájiteľná sanitácia musí navštíviť každé z týchto skrytých miest, nielen stránku, na ktorú ste sa pozerali

Čistenie dokumentu pomocou SanitizeDocument

Knižnica losLab PDF Library reaguje na túto škálu únikov pomocou SanitizeDocument(Flags), čo je riadiace volanie, ktoré spúšťa sadu nezávislých čistení vybraných bitovou maskou a vracia počet aplikovaných čistení. Príznaky sa dajú kombinovať. SANITIZE_JAVASCRIPT odstráni každý balík JavaScriptu na úrovni dokumentu a akýkoľvek skript OpenAction. SANITIZE_ACTIONS odstráni slovník akcií /A z každej anotácie prostredníctvom nového TPDFAnnots.RemoveAnnotAction a vymaže katalóg OpenAction. SANITIZE_METADATA vymaže Author, Subject, Keywords, Creator a Producer v slovníku informácií o dokumente (ISO 32000-1 §14.3.3) aj v toku metadát XMP (§14.3.2). SANITIZE_ANNOTATIONS vymaže každú anotáciu v celom dokumente a SANITIZE_OPEN_ACTION odstráni položku katalógu /OpenAction. SANITIZE_ALL je bitovým zjednotením všetkých piatich

Indexové konvencie vo vnútri týchto čistení sú presne tými vecami, ktoré potichu rozbijú ručne napísanú slučku, čo je hlavný dôvod, prečo SanitizeDocument existuje ako jedno volanie. Zoznam GlobalJavaScriptPackageName sa indexuje od 0 a metóda RemoveGlobalJavaScript zoznam priebežne zmenšuje, takže čistenie JavaScriptu vždy číta index balíka 0 a odstraňuje ho, kým počet nedosiahne nulu. Naproti tomu DeleteAnnotation sa indexuje od 1. AnnotationCount podáva správy pre aktuálne vybranú stránku, takže čistenie anotácií postupne vyberá každú stránku pred vykonaním počítania. losLab PDF Library tieto hranice interne rieši správne, takže namiesto replikovania piatich rôznych pravidiel iterácie odovzdáte jednu sadu príznakov a načítate jeden výsledný počet

var
  Applied: Integer;
begin
  // Strip metadata, scripts, and actions but keep the annotations:
  Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
    SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);

  // Or clear every hidden channel, annotations included:
  Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;

Kompletný pracovný postup redakcie a sanitácie

Keď to spojíte, celý postup je krátky. losLab PDF Library odstraňuje viditeľný obsah pomocou RedactRegion stránku po stránke a potom vymaže každý skrytý kanál pomocou SanitizeDocument(SANITIZE_ALL) predtým, než sa súbor zapíše. Keďže redakcia takmer vždy beží na dokumentoch, ktoré prišli mimo vašej kontroly, oplatí sa ich načítať cez zabezpečenú cestu — rovnaký obranný prístup opísaný v časti bezpečné parsovanie nedôveryhodných PDF súborov —, takže nesprávne formátovaný vstup zlyhá čisto a nie uprostred redakčného procesu

var
  PDF: TPDFlib;
  Applied: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('incoming.pdf', '') = 1 then
    begin
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);       // delete the sensitive line
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // clear metadata, JS, actions, annots
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Kde redakcia toku obsahu končí

Metóda RedactRegion pracuje na toku obsahu, čo je presne miesto, kde žije digitálne vytvorený text, a presne miesto, kde naskenovaný text nežije. Keď je stránka naskenovaným obrázkom, slová sú pixelmi vo vnútri obrázkového XObjectu, nie textovými operátormi, a losLab PDF Library nemôže vyrezať meno zo stredu rastra tak, ako vymazáva glyf. Jej narábanie s obrázkami je zámerne konzervatívne: akýkoľvek obrázok, ktorého umiestnenie pretína obdĺžnik, sa odstráni celý, takže redigovaný box na naskenovanej stránke vezme so sebou celý sken a nie iba jeho časť. Redigovanie oblasti vo vnútri obrázka znamená rasterizáciu, prekreslenie a opätovné vloženie na úrovni pixelov, čo je iná operácia než vymazanie toku obsahu. Rovnaká opatrnosť platí pre vrstvu textu OCR nad naskenovanou stránkou: RedactRegion odstráni textové operátory, ktoré vidí, ale obrázok pod nimi zostane nedotknutý, pokiaľ jeho obrázkový XObject tiež nepretína box

Dva jemné body udržiavajú výsledky presné. Test prieniku odhaduje šírku každého textového úseku z počtu znakov a veľkosti písma a nemeria presné metriky glyfov, takže veľmi krátke slovo napísané proporcionálnym písmom môže preniknúť cez tesne nakreslený obdĺžnik — dajte boxu malý okraj. Vymazanie prebieha aj na úrovni inštrukcií: RedactRegion odstraňuje celé operátory zobrazujúce text, takže obdĺžnik, ktorý zachytí iba časť úseku, môže vziať aj susedné znaky, ktoré s ním zdieľali rovnaký operátor. Ani jedno obmedzenie nie je dôvodom na nedôveru mechanizmu, iba dôvodom na starostlivé ohraničenie obdĺžnika a kontrolu výsledku

Disciplínou, ktorá uzatvára každý redakčný proces, je overenie, nie dôvera. Zredigujte oblasť, vyčistite dokument, uložte do nového súboru, znova ho otvorte a pokúste sa získať späť text, ktorý ste odstránili, aj metadáta, ktoré ste vymazali; ak sa obe vrátia prázdne, redakcia bola úspešná. Rozhrania API RedactRegion a SanitizeDocument zobrazené v tomto dokumente sa dodávajú s knižnicou losLab PDF Library pre Delphi a C++Builder, spolu s úplnou referenciou o modeli obsahu a sanitácii