Tekninen artikkeli

Aito PDF-sensurointi Delphissä: Poista ja puhdista

Oikea PDF-sensurointi (redaction) Delphissä tarkoittaa taustalla olevien tavujen poistamista, ei niiden päälle maalaamista. losLab PDF Library vetää tämän rajan kahdella APIlla: RedactRegion muokkaa sivun sisältövirtaa siten, että poistettu teksti, vektorit ja kuvat ovat fyysisesti poissa, ja SanitizeDocument tyhjentää metatiedot, skriptit ja toiminnot, joihin sensuroitu tieto voi myös piiloutua. Jos epäonnistut jommallakummalla puolella, toimitat tiedoston, joka näyttää näytöllä sensuroidulta, mutta paljastaa salaisuuden jokaiselle, joka kopioi tekstin tai avaa asiakirjan ominaisuudet (Document Properties)

Epäonnistumistapa on yleisesti tiedossa. Sanomalehdet, tuomioistuimet ja valtion virastot ovat kaikki julkaisseet PDF-tiedostoja, joissa nimen tai numeron päälle on asetettu musta suorakulmio, mutta lukija on voinut valita sen alla olevan tekstin, kopioida sen ja liittää sensuroidun merkkijonon näkyville. Suorakulmio oli pelkkä piirros. Teksti oli yhä tallella sisältövirrassa, yhtä kerrosta maalin alapuolella, täysin ehjänä ja valittavissa. losLab PDF Library, Delphi- ja C++Builder-PDF-moottori, joka tarjotaan TPDFlib-luokan kautta, pitää tätä eroa sensuroinnin ytimenä eikä jälkiajatuksena

Miksi mustan laatikon piirtäminen PDF-tekstin päälle ei ole aitoa sensurointia?

losLab PDF Library tarjoaa funktion MaskRect tapauksiin, joissa peittäminen on aidosti sitä, mitä haluat: vesileima, visuaalinen pimennyselementti tai todistusleima. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) liittää suorakulmion sivun sisältövirtaan ja maalaa sen polunrakennusoperaattorilla re, jota seuraa täyttöoperaattori f (ISO 32000-1 §8.5). Jokainen sivulla jo ollut tavu — peittämäsi nimen kirjoitusmerkit, vektoriviivat, upotettu kuva — säilyy täsmälleen siellä missä se oli, yhtä käskyä aiemmin samassa virrassa. Yhteensopiva katseluohjelma piirtää mustan laatikon viimeisenä, joten se voittaa visuaalisesti. Peittäminen ei muuta mitään sen alla olevaa

Kuka tahansa voi palauttaa alkuperäisen sisällön kolmella tavalla: valitsemalla ja kopioimalla tekstin suoraan läpinäkymättömän laatikon läpi, ajamalla tekstinpoiminnan sivun yli tai poistamalla peittävän suorakulmion sisältövirrasta. MaskRect on rehellinen jo nimessään — se peittää (mask), ei poista. Tulos on kuin tarra sanan päällä, jonka voi yhä lukea nostamalla tarran pois

Miten RedactRegion poistaa sisältöä käskytasolla

RedactRegion(Page, Left, Top, Width, Height, Options) toimii kerrosta maalin alapuolella. losLab PDF Library jäsentää sivun jokaisen sisältökerroksen TPDFContentProgram-ohjelmaksi, käy läpi käskylistan CTM-seurantajäsentimellä (sisältömallin FindInstructionsInRect) ja merkitsee jokaisen tekstiä näyttävän, polkua maalaavan ja kuvan piirtävän Do-operaattorin, jonka maalattu alue leikkaa suorakulmiosi. Nämä käskyt poistetaan TPDFContentProgram.Delete-metodilla, ja muokattu kerros kirjoitetaan takaisin WritePageContentLayer-metodilla. Tavut eivät siirry suojan taakse — ne poistuvat virrasta. Sen jälkeen ajettu tekstinpoiminta ei löydä mitään, koska mitään löydettävää ei ole

Kaksi toteutuksen yksityiskohtaa on syytä pitää mielessä. RedactRegion ottaa vastaan vasemman yläkulman suorakulmion nykyisessä alkupisteessä ja yksiköissä ja muuntaa sen sisäisesti sisältövirran käyttämäksi vasemman alakulman käyttäjäavaruudeksi (user space), joten määrität alueen sellaisena kuin näet sen sivulla. Kun suorakulmio kattaa useita käskyjä, RedactRegion poistaa ne laskevassa indeksijärjestyksessä, koska käskyn 4 poistaminen ennen käskyä 7 siirtäisi kaikkia myöhempiä indeksejä ja korruptoisi alueen. Jos välität arvon Options = 0, losLab PDF Library leimaa myös yhtenäisen mustan laatikon tyhjennetyn alueen päälle näkyväksi merkiksi — mutta tämä merkki on kosmeettinen ja se lisätään vasta sen jälkeen, kun todellinen sisältö on jo poistettu

Käytännössä suorakulmiota harvoin kovakoodataan. Etsit sivulta arkaluontoista merkkijonoa ja luet sen rajauslaatikon (bounding box) — sama sivuhaku ja elementtien luettelointi, jota käsitellään artikkelissa tekstin ja sivuelementtien paikallistaminen Delphissä — ja annat sitten kyseisen suorakulmion RedactRegion-metodille:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Poista kaikki, mikä on piirretty 220 x 14 pt laatikon sisään sivulla 1.
      // Koordinaatit ovat vasemmasta yläkulmasta, nykyisessä alkupisteessä ja yksiköissä.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 leimaa mustan merkin
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Missä sensuroitu tieto yhä piilee PDF-tiedostossa?

Sensurointi, joka pysähtyy näkyvään kerrokseen, jättää jäljen, koska PDF tallentaa tietoa paikkoihin, joihin lukija ei koskaan katso. losLab PDF Library ryhmittelee tietovuodot kanaviin, jotka on syytä nimetä. Asiakirjan tietosanakirja (Document Information Dictionary) ja XMP-metatietovirta sisältävät rutiininomaisesti Author-, Producer- tai Keywords-merkkijonon, joka nimeää juuri sen henkilön tai tapauksen, jonka poistit leipätekstistä. Asiakirjatason JavaScript ja luettelon (catalog) OpenAction voivat suorittaa koodia avattaessa ja päästä käsiksi tietoon, jonka luulit jo hävinneen. Jokainen huomautus sisältää valinnaisen /A-toiminnon, ja huomautus itse — tarralappu, vanha lomakekenttä, linkki — voi sijaita suoraan sensuroidun kohdan päällä sisältäen oman kopionsa tekstistä. Salaus ei koske mitään näistä; salattu tiedosto, jossa on selkokielinen XMP-paketti, vuotaa yhä otsikkonsa mille tahansa hakukoneelle, mikä on itsessään syy salausten ja oikeuksien auditointiin. Puolustettavan puhdistuksen on käytävä läpi jokainen näistä piilopaikoista, ei vain sivu, jota katsoit

Asiakirjan puhdistaminen SanitizeDocument-funktiolla

losLab PDF Library vastaa näihin vuotoihin SanitizeDocument(Flags) -kutsulla, joka ajaa bittimaskin valitsemat itsenäiset poistot ja palauttaa sovellettujen poistojen määrän. Lippuja voidaan yhdistellä. SANITIZE_JAVASCRIPT poistaa jokaisen asiakirjatason JavaScript-paketin ja kaikki OpenAction-skriptit. SANITIZE_ACTIONS riisuu /A-toimintosanakirjan jokaisesta huomautuksesta uuden TPDFAnnots.RemoveAnnotAction-metodin kautta ja tyhjentää luettelon OpenAction-merkinnän. SANITIZE_METADATA tyhjentää Author-, Subject-, Keywords-, Creator- ja Producer-kentät sekä Document Information Dictionarysta (ISO 32000-1 §14.3.3) että XMP-metatietovirrasta (§14.3.2). SANITIZE_ANNOTATIONS poistaa jokaisen huomautuksen koko asiakirjasta, ja SANITIZE_OPEN_ACTION poistaa luettelon /OpenAction-merkinnän. SANITIZE_ALL on kaikkien viiden bittikohtainen unioni

Näiden poistojen indeksikäytännöt ovat juuri niitä asioita, jotka rikkovat hiljaisesti itse koodatun silmukan, ja siksi SanitizeDocument on olemassa yhtenä kutsuna. GlobalJavaScriptPackageName on 0-perusteinen, ja RemoveGlobalJavaScript kutistaa luetteloa edetessään, joten JavaScriptin poisto lukee aina paketti-indeksin 0 ja poistaa sitä, kunnes määrä saavuttaa nollan. DeleteAnnotation on sen sijaan 1-perusteinen. AnnotationCount raportoi valittua sivua vastaan, joten huomautusten poistot valitsevat jokaisen sivun vuorotellen ennen laskemista. losLab PDF Library hallitsee nämä rajat sisäisesti oikein, joten välität vain yhden lippujoukon ja luet yhden palautusmäärän sen sijaan, että toistaisit viisi erilaista iterointisääntöä

var
  Applied: Integer;
begin
  // Riisu metatiedot, skriptit ja toiminnot, mutta säilytä huomautukset:
  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;

Täydellinen sensurointi- ja puhdistustyönkulku

Yhdistä nämä kaksi, ja koko menettely on lyhyt. losLab PDF Library poistaa näkyvän sisällön RedactRegion-metodilla sivu sivulta ja tyhjentää sitten jokaisen piilotetun kanavan kutsulla SanitizeDocument(SANITIZE_ALL) ennen tiedoston kirjoittamista. Koska sensurointi suoritetaan lähes aina asiakirjoille, jotka ovat saapuneet hallintasi ulkopuolelta, ne kannattaa ladata suojatun polun kautta — sama puolustuksellinen asento, jota kuvataan artikkelissa turvaton PDF-jäsentäminen ja muistiturvallisuus —, jotta virheellinen syöte epäonnistuu siististi eikä vasta sensurointivaiheen puolivälissä

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);       // poista arkaluonteinen rivi
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // tyhjennä metatiedot, JS, toiminnot, huomautukset
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Mihin sisältövirran sensurointi pysähtyy

RedactRegion toimii sisältövirrassa, mikä on juuri se paikka, jossa digitaalisesti luotu teksti asuu, ja täsmälleen se paikka, jossa skannattu teksti ei asu. Kun sivu on skannattu kuva, sanat ovat pikseleitä kuva-XObjectissa (image XObject), eivät tekstioperaattoreita, eikä losLab PDF Library voi leikata nimeä irti rasterikuvan keskeltä samalla tavalla kuin se poistaa kirjoitusmerkin. Sen kuvankäsittely on tarkoituksella konservatiivista: mikä tahansa kuva, jonka sijainti leikkaa suorakulmion, poistetaan kokonaan, joten sensurointilaatikko skannatulla sivulla vie koko skannauksen mukanaan eikä vain palaa siitä. Alueen sensurointi kuvan sisältä vaatii rasterointia, maalausta ja uudelleen upottamista pikselitasolla, mikä on eri operaatio kuin sisältövirran poisto. Sama varovaisuus pätee skannatun sivun päällä olevaan OCR-tekstikerrokseen: RedactRegion poistaa tekstioperaattorit, jotka se näkee, mutta sen alla oleva kuva säilyy koskemattomana, ellei sen kuva-XObject leikkaa laatikkoa

Kaksi hienompaa seikkaa pitää tulokset luotettavina. Leikkaustesti arvioi kunkin tekstiajon leveyden sen merkkimäärän ja fonttikoon perusteella sen sijaan, että se mittaisi tarkat kirjoitusmerkkien metriikat, joten erittäin lyhyt sana, joka on kirjoitettu suhteellisella fontilla, voi lipsahtaa tiukasti piirretyn suorakulmion ohi — anna laatikolle pieni marginaali. Poisto on myös käskykohtaista: RedactRegion poistaa kokonaisia tekstiä näyttäviä operaattoreita, joten suorakulmio, joka rajaa vain osan ajosta, voi viedä mukanaan viereiset merkit, jotka jakoivat saman operaattorin. Kumpikaan rajoitus ei ole syy epäillä mekanismia, vasta ainoastaan syy rajata suorakulmio huolellisesti ja tarkistaa tulos

Jokaisen sensurointityönkulun päättävä kurinalaisuus on varmistaminen, ei luottamus. Sensuroi alue, puhdista asiakirja, tallenna uuteen tiedostoon, avaa se uudelleen ja yritä vetää takaisin sekä poistamasi teksti että tyhjentämäsi metatiedot; kun molemmat palautuvat tyhjinä, sensurointi piti. Tässä esitetyt RedactRegion- ja SanitizeDocument-APIt toimitetaan Delphille ja C++Builderille tarkoitetun losLab PDF Library -kirjaston mukana, yhdessä täydellisen sisältömallin ja puhdistusviitteen kanssa