Tehnički članak

Prava PDF redakcija u Delphi-ju: Brisanje i čišćenje (sanitizacija)

Stvarna PDF redakcija (redaction) u Delphi-ju znači brisanje osnovnih bajtova, a ne samo crtanje preko njih. losLab PDF biblioteka povlači tu liniju sa dva API-ja: metoda RedactRegion menja tok sadržaja (content stream) stranice tako da uklonjeni tekst, vektori i slike fizički nestaju, a metoda SanitizeDocument čisti metapodatke, skripte i akcije gde se redigovane informacije takođe mogu skrivati. Ako pogrešite u bilo kom od ova dva dela, isporučićete datoteku koja na ekranu izgleda cenzurisano, ali vraća tajnu svakome ko kopira tekst ili otvori svojstva dokumenta (Document Properties)

Ovaj način otkazivanja je opštepoznata stvar. Novine, sudovi i vladine agencije su puštali PDF dokumente sa crnim pravougaonikom postavljenim preko imena ili broja, da bi potom čitalac jednostavno označio tekst ispod njega, kopirao ga i nalepio navodno redigovani string na videlo. Pravougaonik je bio samo crtež. Tekst je i dalje bio tamo, sedeći u toku sadržaja jedan sloj ispod boje, potpuno netaknut i spreman za selekciju. losLab PDF biblioteka, mehanizam za PDF u Delphi-ju i C++Builder-u izložen kroz klasu TPDFlib, tretira ovu razliku kao samu poentu redakcije, a ne kao sporednu misao

Zašto crtanje crnog okvira preko PDF teksta nije stvarna redakcija?

losLab PDF biblioteka izlaže metodu MaskRect za slučajeve u kojima je pokrivanje zaista ono što želite: vodeni žig, vizuelno zatamnjenje ili kontrolni pečat. Metoda MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) dodaje pravougaonik u tok sadržaja stranice i crta ga pomoću operatora za konstrukciju putanje re, nakon čega sledi operator popunjavanja f (ISO 32000-1 §8.5). Svaki bajt koji je već bio na stranici — glifovi imena koje ste pokrili, vektorski potezi, ugrađena slika — ostaje tačno tamo gde je i bio, jednu instrukciju ranije u istom toku. Pregledač koji je usklađen sa standardom iscrtava crni okvir poslednji, tako da on vizuelno pobeđuje. Pokrivanje ne menja ništa ispod sebe

Svako može da povrati originalni sadržaj na tri načina: označi i kopira tekst direktno kroz neprovidni okvir, pokrene ekstraktor teksta nad stranicom ili obriše pokrivajući pravougaonik iz toka sadržaja. Metoda MaskRect je iskrena u svom nazivu — ona maskira, a ne uklanja. Rezultat je nalepnica preko reči koju i dalje možete pročitati ako podignete nalepnicu

Kako RedactRegion briše sadržaj na nivou instrukcija

Metoda RedactRegion(Page, Left, Top, Width, Height, Options) radi jedan sloj ispod boje. losLab PDF biblioteka parsira svaki sloj sadržaja stranice u TPDFContentProgram, prolazi kroz listu instrukcija pomoću analizatora koji prati CTM (matrica tekuće transformacije) (FindInstructionsInRect u modelu sadržaja) i označava svaki operator prikaza teksta, crtanja putanje i slikovnog operatora Do čija se nacrtana oblast preseca sa vašim pravougaonikom. Te instrukcije se uklanjaju pomoću TPDFContentProgram.Delete, a izmenjeni sloj se upisuje nazad pomoću WritePageContentLayer. Bajtovi ne klize iza poklopca — oni napuštaju tok. Ekstraktor teksta pokrenut nakon toga ne pronalazi ništa, jer nema šta da se pronađe

Dva detalja o implementaciji vredi uzeti u obzir. Metoda RedactRegion uzima pravougaonik sa gornjim levim početkom u trenutnom koordinatnom početku i jedinicama mera i interno ga konvertuje u korisnički prostor sa donjim levim početkom koji koristi tok sadržaja, tako da navodite regiju onako kako je vidite na stranici. Kada pravougaonik pokriva više instrukcija, RedactRegion ih briše u opadajućem redosledu indeksa, jer bi uklanjanje instrukcije 4 pre instrukcije 7 pomerilo svaki kasniji indeks i pokvarilo opseg. Prosledite Options = 0 i losLab PDF biblioteka će takođe utisnuti pun crni okvir preko očišćene regije kao vidljivi marker — ali taj marker je kozmetičke prirode, primenjen nakon što je stvarni sadržaj već uklonjen

U praksi retko hardkodirate pravougaonik. Pretražujete stranicu za osetljivi string i očitate njegov granični okvir (bounding box) — ista pretraga stranica i enumeracija elemenata koji su pokriveni u članku o pronalaženju teksta i elemenata stranice u Delphi-ju — a zatim taj pravougaonik prosleđujete metodi 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;

Gde se redigovane informacije još uvek skrivaju u PDF-u?

Redakcija koja se zaustavlja na vidljivom sloju ostavlja trag, jer PDF čuva informacije na mestima koja čitalac nikada ne gleda. losLab PDF biblioteka grupiše ova curenja u kanale koje vredi imenovati. Rečnik informacija o dokumentu (Document Information Dictionary) i XMP tok metapodataka rutinski nose podatke kao što su Author, Producer ili Keywords string koji imenuje upravo osobu ili slučaj koji ste uklonili iz tela dokumenta. JavaScript na nivou dokumenta i OpenAction kataloga mogu se izvršiti pri otvaranju i doći do podataka za koje ste mislili da su nestali. Svaka anotacija nosi opcionu akciju /A, a sama anotacija — lepljiva beleška, zastarelo polje obrasca, veza — može sedeti direktno preko redigovanog mesta sa sopstvenom kopijom teksta. Ništa od ovoga nije zaštićeno enkripcijom. Šifrovana datoteka sa XMP paketom u čistom tekstu (plaintext) i dalje odaje svoj naslov svakom indekseru, što je praznina koju vredi ispitati u članku o reviziji enkripcije i dozvola. Pouzdana sanitizacija mora posetiti svako od ovih mesta skrivanja, a ne samo stranicu koju ste gledali

Čišćenje dokumenta pomoću SanitizeDocument

losLab PDF biblioteka odgovara na ovaj niz curenja metodom SanitizeDocument(Flags), orkestracijskim pozivom koji pokreće skup nezavisnih čišćenja izabranih bitmaskom i vraća broj primenjenih čišćenja. Zastavice (flags) se mogu kombinovati. SANITIZE_JAVASCRIPT uklanja svaki JavaScript paket na nivou dokumenta i svaku OpenAction skriptu. SANITIZE_ACTIONS uklanja rečnik akcija /A iz svake anotacije, preko nove metode TPDFAnnots.RemoveAnnotAction, i prazni OpenAction kataloga. SANITIZE_METADATA briše polja Author, Subject, Keywords, Creator i Producer i u rečniku informacija o dokumentu (ISO 32000-1 §14.3.3) i u XMP toku metapodataka (§14.3.2). SANITIZE_ANNOTATIONS briše svaku anotaciju u celom dokumentu, a SANITIZE_OPEN_ACTION uklanja unos /OpenAction iz kataloga. SANITIZE_ALL predstavlja bitovnu uniju svih pet zastavica

Konvencije o indeksima unutar ovih čišćenja su upravo ona vrsta stvari koja tiho kvari ručno napisanu petlju, što je uglavnom i razlog zašto SanitizeDocument postoji kao jedan poziv. GlobalJavaScriptPackageName koristi bazu 0, a RemoveGlobalJavaScript smanjuje listu kako se izvršava, tako da čišćenje JavaScript-a uvek čita indeks paketa 0 i uklanja ga dok broj ne dostigne nulu. Nasuprot tome, DeleteAnnotation koristi bazu 1. AnnotationCount prijavljuje stanje za trenutno izabranu stranicu, tako da čišćenje anotacija bira svaku stranicu redom pre brojanja. losLab PDF biblioteka interno ispravno rukuje ovim granicama, tako da prosleđujete jedan skup zastavica i dobijate nazad jedan broj, umesto da reprodukujete pet različitih pravila iteracije

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;

Kompletan radni tok redakcije i sanitizacije

Spojite ovo dvoje i kompletna procedura postaje kratka. losLab PDF biblioteka uklanja vidljivi sadržaj pomoću RedactRegion, stranicu po stranicu, a zatim čisti svaki skriveni kanal pomoću SanitizeDocument(SANITIZE_ALL) pre nego što se datoteka upiše na disk. Uzimajući u obzir da se redakcija skoro uvek vrši nad dokumentima koji su stigli van vaše kontrole, vredi ih učitati kroz bezbedan put — isti odbrambeni stav opisan u članku o bezbednom parsiranju nepouzdanih PDF dokumenata — tako da neispravan ulaz neuspešno završi rad čisto, umesto da pukne na pola puta kroz redakciju

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;

Gde se redakcija toka sadržaja zaustavlja

Metoda RedactRegion radi na toku sadržaja, što je upravo mesto gde živi digitalno kreiran tekst (born-digital text), i upravo mesto gde skenirani tekst ne živi. Kada je stranica skenirana slika, reči su pikseli unutar slikovnog objekta XObject, a ne tekstualni operatori, i losLab PDF biblioteka ne može da izreže ime iz sredine rastera na način na koji briše glif. Njeno rukovanje slikama je namerno konzervativno: svaka slika čije se postavljanje preseca sa pravougaonikom uklanja se u celosti, tako da okvir za redakciju na skeniranoj stranici nosi sa sobom ceo sken, a ne samo njegov deo. Redigovanje regije unutar slike znači rasterizaciju, bojenje i ponovno ugrađivanje na nivou piksela, što je drugačija operacija od brisanja toka sadržaja. Isti oprez važi i za OCR tekstualni sloj preko skenirane stranice: RedactRegion uklanja tekstualne operatore koje vidi, ali slika ispod ostaje netaknuta osim ako se njen slikovni XObject takođe ne preseca sa okvirom

Dve finese održavaju rezultate pouzdanim. Test preseka procenjuje širinu svakog teksta na osnovu broja znakova i veličine fonta, umesto da meri tačnu metriku glifova, tako da vrlo kratka reč napisana proporcionalnim fontom može promaći tik pored tesno iscrtanog pravougaonika — dajte okviru malu marginu. Brisanje je takođe granularno na nivou instrukcija: RedactRegion uklanja čitave operatore za prikaz teksta, tako da pravougaonik koji odseca samo deo teksta može ukloniti i susedne znakove koji su delili isti operator sa njim. Nijedna granica nije razlog za nepoverenje u mehanizam, već samo razlog da pažljivo definišete pravougaonik i proverite rezultat

Disciplina koja zatvara svaki radni tok redakcije je verifikacija, a ne poverenje. Redigujte regiju, očistite dokument, sačuvajte u novu datoteku, ponovo je otvorite i pokušajte da povratite tekst koji ste uklonili i metapodatke koje ste očistili. Kada oba pokušaja vrate prazan rezultat, redakcija je uspela. API-ji RedactRegion i SanitizeDocument koji su ovde prikazani isporučuju se uz losLab PDF biblioteku za Delphi i C++Builder, zajedno sa kompletnom referencom za model sadržaja i sanitizaciju