Techninis straipsnis

Tikrasis PDF nuasmeninimas (redakcija) Delphi aplinkoje: trynimas ir valymas

Tikrasis PDF nuasmeninimas (redakcija) Delphi aplinkoje reiškia pagrindinių baitų ištrynimą, o ne tiesiog jų užpiešimą. losLab PDF biblioteka brėžia šią ribą naudodama dvi API: RedactRegion redaguoja puslapio turinio srautą taip, kad pašalintas tekstas, vektoriai ir vaizdai fiziškai išnyksta, o SanitizeDocument išvalo metaduomenis, scenarijus ir veiksmus, kuriuose taip pat slepiasi nuasmeninta informacija. Suklyskite kurioje nors dalyje ir išsiųsite failą, kuris ekrane atrodo cenzūruotas, tačiau išduoda paslaptį visiems, kas nukopijuoja tekstą arba atidaro dokumento savybes (Document Properties)

Ši klaidos elgsena yra vieša paslaptis. Laikraščiai, teismai ir valstybinės institucijos yra išleidusios PDF failų, kuriuose virš vardo ar skaičiaus uždėtas juodas stačiakampis, tačiau skaitytojui pakanka pasirinkti tekstą po juo, jį nukopijuoti ir įklijuoti tariamai nuasmenintą eilutę visiems matomoje vietoje. Stačiakampis buvo tik piešinys. Tekstas vis dar buvo ten, turinio sraute vienu sluoksniu žemiau dažų, visiškai nepažeistas ir pilnai pasirenkamas. losLab PDF biblioteka, Delphi ir C++Builder PDF variklis, pasiekiamas per TPDFlib klasę, traktuoja šį skirtumą kaip esminį nuasmeninimo tikslą, o ne kaip pavėluotą mintį

Kodėl juodos dėžutės nupiešimas ant PDF teksto nėra tikrasis nuasmeninimas?

losLab PDF biblioteka pateikia MaskRect tiems atvejams, kai uždengimas yra būtent tai, ko norite: vandens ženklui, vizualiam užtemdymui, bandomajam spaudui. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) prideda stačiakampį prie puslapio turinio srauto ir nupiešia jį su re kelio konstravimo operatoriumi, po kurio seka f užpildymo operatorius (ISO 32000-1 §8.5). Kiekvienas baitas, kuris jau buvo puslapyje — jūsų uždengto vardo glifai, vektoriniai potėpiai, įterptas vaizdas — lieka tiksliai ten, kur buvo, viena instrukcija anksčiau tame pačiame sraute. Reikalavimus atitinkanti peržiūros programa nupiešia juodą dėžutę paskiausiai, zodžiu, ji laimi vizualiai. Uždengimas nieko po juo nepakeičia

Bet kas gali atkurti pradinį turinį trimis būdais: pasirinkti ir nukopijuoti tekstą tiesiai per nepermatomą dėžutę, paleisti teksto išgavimo programą puslapyje arba ištrinti uždengiantį stačiakampį iš turinio srauto. MaskRect apie tai sąžiningai sako savo pavadinimu — jis maskuoja, o ne pašalina. Rezultatas yra lipdukas ant žodžio, kurį vis tiek galite perskaityti nukėlę lipduką

Kaip RedactRegion ištrina turinį instrukcijų lygiu

RedactRegion(Page, Left, Top, Width, Height, Options) veikia vienu sluoksniu žemiau dažų. losLab PDF biblioteka išanalizuoja kiekvieną puslapio turinio sluoksnį į TPDFContentProgram, pereina instrukcijų sąrašą naudodama CTM sekimo analizatorių (FindInstructionsInRect turinio modelyje) ir pažymi kiekvieną tekstą rodantį, kelią piešiantį ir vaizdo Do operatorių, kurio nupieštas plotas kertasi su jūsų stačiakampiu. Šios instrukcijos pašalinamos naudojant TPDFContentProgram.Delete, o redaguotas sluoksnis įrašomas atgal su WritePageContentLayer. Baitai nepaslenka po uždengimu — jie palieka srautą. Po to paleista teksto išgavimo programa nieko neranda, nes nėra ką rasti

Du diegimo aspektai yra verti jūsų dėmesio. RedactRegion priima viršutinį-kairįjį stačiakampį pagal esamą koordinačių pradžią bei vienetus ir viduje konvertuoja jį į apatinę-kairiąją vartotojo erdvę, kurią naudoja turinio srautas, todėl nurodote sritį taip, kaip ją matote puslapyje. Kai stačiakampis apima kelias instrukcijas, RedactRegion ištrina jas mažėjančia indeksų tvarka, nes pašalinus 4 instrukciją prieš 7 instrukciją, pasikeistų kiekvienas vėlesnis indeksas ir būtų sugadintas diapazonas. Perduokite Options = 0 ir losLab PDF biblioteka taip pat uždės vientisą juodą dėžutę virš išvalytos srities kaip matomą žymeklį — tačiau šis žymeklis yra kosmetinis, pritaikomas po to, kai tikrasis turinys jau yra pašalintas

Praktikoje retai kada įrašote griežtas stačiakampio koordinates. Jūs ieškote jautrios eilutės puslapyje ir nuskaitote jos ribojantį rėmelį — ta pati puslapio paieška ir elementų išvardijimas aprašyti straipsnyje teksto ir puslapio elementų vietos nustatymas Delphi aplinkoje —, tada perduodate tą stačiakampį metodui 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;

Kur PDF failuose vis dar slepiasi nuasmeninta informacija?

Nuasmeninimas, kuris sustoja ties matomu sluoksniu, palieka pėdsakus, nes PDF saugo informaciją vietose, į kurias skaitytojas niekada nežiūri. losLab PDF biblioteka suskirsto šiuos nutekėjimus į kanalus, kuriuos verta įvardyti. Dokumento informacijos žodynas (Document Information Dictionary) ir XMP metaduomenų srautas įprastai turi autoriaus (Author), gamintojo (Producer) arba raktinių žodžių (Keywords) eilutę, kurioje įvardijamas tas pats asmuo ar atvejis, kurį pašalinote iš dokumento kūno. Dokumento lygio „JavaScript“ ir katalogo „OpenAction“ gali būti vykdomi atidarius dokumentą ir pasiekti duomenis, kuriuos manėte pašalinę. Kiekviena anotacija turi pasirinktinį /A veiksmą, o pati anotacija — lipnus lapelis, pasenęs formos laukas, nuoroda — gali būti tiesiai ant nuasmenintos vietos su savo teksto kopija. Šių dalykų šifravimas neliečia; užšifruotas failas su atviro teksto XMP paketu vis tiek nutekina savo pavadinimą bet kokiam indeksuotojui, o šią spragą verta ištirti atskirai, kaip aprašyta straipsnyje apie šifravimo ir teisių auditą. Patikimas valymas turi aplankyti kiekvieną iš šių slėptuvių, o ne tik puslapį, į kurį žiūrėjote

Dokumento valymas naudojant SanitizeDocument

losLab PDF biblioteka atsako į šią nutekėjimų įvairovę su SanitizeDocument(Flags) — orkestravimo iškvietimu, kuris paleidžia nepriklausomų valymų rinkinį, pasirinktą bitų kauke, ir grąžina pritaikytų valymų skaičių. Vėliavėlės yra derinamos. SANITIZE_JAVASCRIPT pašalina kiekvieną dokumento lygio „JavaScript“ paketą ir bet kokį „OpenAction“ scenarijų. SANITIZE_ACTIONS pašalina /A veiksmų žodyną iš kiekvienos anotacijos per naująją TPDFAnnots.RemoveAnnotAction ir išvalyti katalogo „OpenAction“. SANITIZE_METADATA ištrina laukus „Author“, „Subject“, „Keywords“, „Creator“ ir „Producer“ tiek dokumento informacijos žodyne (ISO 32000-1 §14.3.3) tiek XMP metaduomenų sraute (§14.3.2). SANITIZE_ANNOTATIONS ištrina visas anotacijas visame dokumente, o SANITIZE_OPEN_ACTION pašalina katalogo įrašą /OpenAction. SANITIZE_ALL yra visų penkių vėliavėlių bitinė sąjunga

Indeksavimo konvencijos šiuose valymuose yra būtent tie dalykai, kurie tyliai sugadina rankiniu būdu sukurtą ciklą, ir tai yra pagrindinė priežastis, kodėl SanitizeDocument egzistuoja kaip vienas iškvietimas. GlobalJavaScriptPackageName indeksuojama nuo 0, o RemoveGlobalJavaScript sumažina sąrašą jam traukiantis, todėl JavaScript valymas visada nuskaito paketo indeksą 0 ir šalina, kol skaičius pasiekia nulį. Priešingai, DeleteAnnotation indeksuojama nuo 1. AnnotationCount pateikia ataskaitą apie šiuo metu pasirinktą puslapį, todėl anotacijų valymai prieš skaičiuodami paeiliui pasirenka kiekvieną puslapį. losLab PDF biblioteka teisingai valdo šias ribas viduje, todėl perduodate vieną vėliavėlių rinkinį ir nuskaitote vieną rezultatą, užuot atkūrę penkias skirtingas pasikartojimo taisykles

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;

Pilna nuasmeninimo ir valymo darbo eiga

Sujunkite abu elementus ir visa procedūra bus trumpa. losLab PDF biblioteka puslapis po puslapio pašalina matomą turinį su RedactRegion, o tada, prieš įrašant failą, išvalo kiekvieną paslėptą kanalą su SanitizeDocument(SANITIZE_ALL). Kadangi nuasmeninimas beveik visada atliekamas su dokumentais, gautais iš išorės, kurių negalite kontroliuoti, verta juos įkelti per apsaugotą kelią — ta pati apsaugos strategija aprašyta straipsnyje apie saugu nepatikimų PDF analizavimą —, kad sugadinta įvestis sugestų švariai, o ne įpusėjus nuasmeninimo procesui

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;

Kur sustoja turinio srauto nuasmeninimas

RedactRegion veikia turinio sraute, kuriame kaip tik ir randasi skaitmeniniu būdu sukurtas tekstas, ir kuriame visiškai nėra nuskaityto teksto. Kai puslapis yra nuskaitytas vaizdas, žodžiai yra vaizdo elementai vaizdo XObject viduje, o ne teksto operatoriai, ir losLab PDF biblioteka negali iškirpti vardo iš rastro vidurio taip, kaip ji ištrina glifą. Jos vaizdo apdorojimas yra sąmoningai konservatyvus: bet koks vaizdas, kurio išdėstymas kertasi su stačiakampiu, pašalinamas visas, todėl nuasmeninimo langelis nuskaitytame puslapyje pašalina visą nuskaitytą vaizdą, o ne jo dalį. Srities nuasmeninimas vaizde reiškia rastravimą, uždažymą ir pakartotinį įterpimą pikselių lygiu, o tai yra kitokia operacija nei turinio srauto trynimas. Toks pat atsargumas galioja ir OCR tekstiniam sluoksniui virš nuskaityto puslapio: RedactRegion pašalina teksto operatorius, kuriuos mato, tačiau po juo esantis paveikslėlis lieka nepaliestas, nebent jo vaizdo XObject taip pat kertasi su langeliu

Du smulkesni aspektai padeliuoti išlaikyti rezultatus teisingus. Sankirtos testas apytiksliai įvertina kiekvieno teksto fragmento plotį pagal jo simbolių skaičių ir šrifto dydį, užuot matavęs tikslią glifų metriką, todėl labai trumpas žodis, parašytas proporcingu šriftu, gali praslysti pro glaudžiai nubrėžtą stačiakampį — suteikite langeliui nedidelę atsargą. Trynimas taip pat vyksta instrukcijų lygmeniu: RedactRegion pašalina ištisus teksto rodymo operatorius, kuriuos iškvietėte, todėl stačiakampis, apimantis tik dalį fragmento, gali pašalinti ir gretimus simbolius, kurie bendrino tą patį operatorių. Nė viena iš šių ribų nėra priežastis nepasitikėti mechanizmu, o tik priežastis kruopščiai parinkti stačiakampio ribas ir patikrinti rezultatą

Disciplina, kuri užbaigia kiekvieną nuasmeninimo eigą, yra tikrinimas, o ne pasitikėjimas. Nuasmeninkite sritį, išvalykite dokumentą, išsaugokite naujame faile, vėl jį atidarykite ir pabandykite atkurti tiek pašalintą tekstą, tiek išvalytus metaduomenis; kai abu sugrįžta tušti, nuasmeninimas pavyko. Čia parodytos RedactRegion ir SanitizeDocument API platinamos kartu su losLab PDF Library, skirtos Delphi ir C++Builder, kartu su pilnu turinio modelio ir valymo aprašymu