Delovno okolje za pregled prejetih PDF-jev je majhen program z eno samo nalogo: pregledati vsako datoteko, preden jo lahko obdelajo drugi sistemi v nadaljnjem procesu. Za opravljanje te naloge mora v enem prehodu združiti več zmogljivosti. Datoteko odpre (ne da bi ji zaupal), prebere podatke, ki jih datoteka navaja o sebi, poišče vsebino, ki bi lahko zavajala preprost ekstraktor ali vsebovala napad, ugotovi, ali sploh obstaja besedilo, ki ga je mogoče ekstrahirati, in nato dokument usmeri v ustrezno vrsto glede na ugotovitve. Če pregled izpustite, so napake neopazne: PDF, šifriran z lastniškim geslom, ki vsebuje obrazec XFA, gre skozi ekstraktor besedila kot prazni nizi, se indeksira kot prazen dokument in nihče ne opazi ničesar, dokler nekdo kasneje ne začne iskati vsebine, ki sploh ni bila prebrana. PDFium Component je knjižnica za ogled in pregledovanje z izvorno kodo VCL/LCL za Delphi, C++Builder in Lazarus ter omogoča klicanje introspekcijskih funkcij, ki jih to delovno okolje potrebuje. V nadaljevanju si bomo ogledali, kateri klici odgovorijo na posamezna vprašanja, in dve mesti, kjer vam očitni klici vrnejo povsem napačne odgovore.
Pet vprašanj, na katera je treba odgovoriti pred usmerjanjem datoteke
Če odmislimo mrežo in trak sličic, se triaža prejetih datotek omeji na pet vprašanj:
- Ali je datoteko sploh mogoče odpreti in pod katerim geslom?
- Kaj trdi, da je: naslov, avtor, datum nastanka?
- Ali vsebuje aktivno ali tvegano vsebino, kot so JavaScript, obrazec XFA ali vgrajene datoteke?
- Ali vsebuje besedilo, ki ga je mogoče ekstrahirati, ali gre za optično branje (sken), namenjeno za OCR?
- Glede na vse to, v katero čakalno vrsto gre datoteka: neposredna obdelava, ročni pregled ali karantena?
Vsako vprašanje se preslika v enega ali dva klica v komponenti PDFium Component. Dve izmed teh preslikav imata ostre robove, ki povzročajo večino napačno usmerjenih datotek, ki sem jih moral odpravljati v produkciji. Metapodatki dokumenta se nahajajo na dveh različnih mestih, ki se lahko razlikujeta, šifriranje pa dokumentu ne preprečuje nujno odpiranja.
Poceni odpiranje: onemogočeno izpolnjevanje obrazcev, brez izrisanih strani
Triaža bi morala biti čim bolj varčna pri odpiranju. Nastavitev FormFill := False pred Active := True komponenti naroči, naj v celoti preskoči okolje za izpolnjevanje obrazcev. To skrajša čas nalaganja in (kar je enako pomembno za datoteke neznanega izvora) prepreči zagon kakršnega koli JavaScripta na ravni dokumenta. Nobena od lastnosti pregledovanja, uporabljenih v nadaljevanju, ne zahteva izrisa strani, zato triažni prehod nikoli ne potrebuje izdelave ene same bitne slike.
procedure InspectIncoming(const IncomingPath: string; var Rec: TIntakeRecord);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := IncomingPath;
Pdf.FormFill := False; // no form environment, no JavaScript init
Pdf.Active := True; // failure is silent: Active simply stays False
if not Pdf.Active then
begin
Rec.OpenFailed := True; // damaged file or user-password lock
Exit; // the finally block still runs
end;
Rec.PageCount := Pdf.PageCount;
CollectIdentity(Pdf, IncomingPath, Rec);
CollectRiskSignals(Pdf, Rec);
finally
Pdf.Active := False;
Pdf.Free; // never leak the instance on a malformed file
end;
end;
Preverjanje po dodelitvi ni neobvezno in je z razlogom preverjanje in ne ravnalnik izjem. Ko mehanizem ne more naložiti datoteke, komponenta zadrži notranjo napako EPdfError in pusti Active na False, namesto da bi jo razširila naprej. Koda, ki čaka na izjemo, bo z veseljem prebrala PageCount iz dokumenta, ki se sploh ni odprl. Če potek zavrnitve potrebuje dejansko besedilo napake mehanizma, preberite datoteko v polje bajtov in pokličite preobloženo metodo LoadDocument, ki sprejme TBytes; ta pot sproži izjemo EPdfError s sporočilom, vključno s primerom z geslom. Blok try..finally je še vedno nujen. Storitve za sprejem datotek delujejo brez nadzora več tednov, nobena poznejša izjema pa ne sme povzročiti uhajanja instance TPdf ali zaklepanja datoteke, ob katero bi se spotaknil ponovni prehod.
Prepustnost redko postane ozko grlo. Z onemogočenim izpolnjevanjem obrazcev in brez izrisa je odpiranje pri triaži odvisno predvsem od vhodno-izhodnih operacij (I/O), en sam delavec pa zlahka pregleda več datotek na sekundo z lokalnega diska. Če obseg prejetih datotek kdaj preseže zmogljivost enega delavca, delo razdelite po datotekah in ne po preverjanjih. Vseh pet vprašanj si deli isto odpiranje, njihovo razdeljevanje med procese pa bi najdražji korak pomnožilo, namesto da bi ga amortiziralo.
Metapodatki se nahajajo na dveh mestih in se razlikujejo
Standard ISO 32000-1 določa dve mesti za metapodatke dokumenta: slovar informacij o dokumentu (razdelek 14.3.3) in paket XMP, pripet katalogu (razdelek 14.3.2). Lastnosti Title, Author, Subject in CreationDate berejo slovar Info, pri čemer se MetaText[] uporablja za kateri koli drug ključ, DecodeDate pa za razčlenjevanje niza datuma v obliki D:YYYYMMDD.... Težava je v tem, da sodobni generatorji vse pogosteje zapisujejo samo XMP, kar standard ISO 32000-2 v PDF 2.0 opredeljuje kot uradno smer z opustitvijo večine ključev v slovarju Info. Simptom v triažnem orodju je konkreten. Vaše delovno okolje prikazuje prazen naslov, medtem ko ga Adobe Acrobat prikaže, ker je Acrobat uporabil rezervno možnost dc:title znotraj paketa XMP, ki se je lastnosti slovarja Info nikoli ne dotaknejo.
procedure CollectIdentity(Pdf: TPdf; const FilePath: string;
var Rec: TIntakeRecord);
begin
Rec.Title := Pdf.Title; // Info dictionary value
Rec.Author := Pdf.Author;
Rec.CreatedAt := Pdf.CreationDate; // raw PDF date string ("D:2026...")
// An empty Info title does not mean the document is untitled. The
// component does not expose the XMP packet, so probe the raw file
// bytes for the dc:title element before trusting the blank.
if (Rec.Title = '') and FileContainsText(FilePath, 'dc:title') then
Include(Rec.Flags, ifTitleInXmpOnly);
end;
Tudi zgoraj opisano preprosto iskanje podniza opravi svoje delo: podatek "metapodatki so prisotni, a ne tam, kjer jih iščejo starejša orodja" je pomemben za usmerjanje v katerem koli arhivskem procesu, ki indeksira po naslovu ali avtorju. Če vaš indeks v nadaljnjem procesu bere samo slovar Info, bodo datoteke, označene na ta način, tiho postale neizsledljive.
Šifrirane datoteke, ki se kljub temu odprejo
Šifriran dokument se ne odpre nujno z napako. Standardni varnostni upravitelj (ISO 32000-1, razdelek 7.6.3) razlikuje uporabniško geslo (potrebno za odpiranje dokumenta) od lastniškega gesla, ki zgolj omejuje dovoljenja, kot sta tiskanje in kopiranje. Velik del "zaščitenih" poslovnih dokumentov je šifriran z lastniškim geslom in praznim uporabniškim geslom. Odprejo se brez opozorila, se v celoti dešifrirajo in se zanašajo na to, da bodo pregledovalniki prostovoljno upoštevali zastavice z dovoljenji. To so pravila uporabe, ne pa zaščita, zato bi morala stanja pri sprejemu dokumentov odražati to razliko.
Zaznavanje šifriranja po uspešnem odpiranju zahteva en klic mehanizma in rezervno možnost. Funkcija FPDF_GetSecurityHandlerRevision(Pdf.Document) vrne -1 za nezaščitene datoteke in revizijo upravitelja v nasprotnem primeru, maska Pdf.Permissions, ki vrne vrednost, ki ni enaka maski z vsemi nastavljenimi biti $FFFFFFFF, pa je potrditveni signal. Za datoteke, ki so resnično zaklenjene z uporabniškim geslom, dodelite Password pred nastavitvijo Active := True; če se odpiranje še vedno ne posreči, usmerite datoteko v blokirano stanje, ki od pošiljatelja zahteva poverilnice prek varnega kanala, namesto da bi poskušali znova na slepo. In uprite se skušnjavi, da bi "šifrirano" samodejno obravnavali kot karanteno. V večini panog z velikim obsegom dokumentov so šifrirane, a odprte datoteke običajen in ne sumljiv pojav.
Aktivna vsebina: JavaScript, XFA in vgrajene datoteke
Tri ugotovitve bi morale vedno vplivati na odločitev o usmerjanju. Prvič, JavaScript: dogodek OnUnsupportedFeature poroča o strukturnih funkcijah, kot sta XFA ali 3D-vsebina, ko naleti nanje, vendar ne zazna JavaScripta. Namesto tega preverite JavaScriptActionCount in vsak rezultat, ki ni nič, obravnavajte kot aktivno vsebino. Drugič, XFA: ko FormType vrne ftXfaFull, so vidne strani pogosto le izris predloge XFA, običajno ekstrahiranje besedila pa bo zaznalo le predlogo namesto izpolnjenih vrednosti. Tretjič, priloge: PDF je format vsebnika in AttachmentCount vam pove, ali ta prenaša dodatne datoteke.
procedure CollectRiskSignals(Pdf: TPdf; var Rec: TIntakeRecord);
var
i, PageNo: Integer;
Ext: string;
begin
Rec.IsEncrypted := Assigned(FPDF_GetSecurityHandlerRevision) and
(FPDF_GetSecurityHandlerRevision(Pdf.Document) <> -1);
Rec.HasForms := Pdf.FormType <> ftNone;
Rec.IsXfa := Pdf.FormType = ftXfaFull;
Rec.HasJavaScript := Pdf.JavaScriptActionCount > 0;
// AnnotationCount is a per-page property; walk the pages to total
// it. Loading a page object renders nothing, so this stays cheap.
Rec.Annotations := 0;
for PageNo := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := PageNo;
Inc(Rec.Annotations, Pdf.AnnotationCount);
end;
Rec.Attachments := Pdf.AttachmentCount;
for i := 0 to Rec.Attachments - 1 do
begin
Ext := LowerCase(ExtractFileExt(string(Pdf.AttachmentName[i])));
if (Ext = '.exe') or (Ext = '.js') or (Ext = '.vbs') or (Ext = '.dll') then
Include(Rec.Flags, ifDangerousAttachment);
end;
end;
Dve podrobnosti v tej zanki si zaslužita pozornost. Ime priloge izhaja iz notranjosti dokumenta, zato ga nikoli ne uporabite kot izhodno pot brez predhodnega čiščenja; vgrajeno ime, kot je ..\..\start.exe, predstavlja tveganje za prehod imenika (path traversal) ob neprevidnem klicu shranjevanja. Seznam prepovedanih končnic pa je le opozorilo, ne pa zagotovilo. Njegova naloga je sprožiti človeško odločitev, ne pa potrditi varnost datoteke.
Pretvarjanje signalov v stanja usmerjanja
Uporaben model stanj potrebuje manj stanj, kot pričakuje večina ekip: ready (brez blokad, besedilo je prisotno), review (odpiranje je uspelo, vendar je potreben pregled zaradi obrazca XFA, JavaScripta, prazne plasti besedila ali naslova le v metapodatkih XMP), blocked (zahtevano uporabniško geslo) in damaged (odpiranje ni uspelo). Dokaze zabeležite skupaj s stanjem. Zgoščena vrednost (hash) datoteke, število strani, natančne zastavice in sporočilo o napaki mehanizma za poškodovane datoteke so pomembni podatki, saj bo oseba, ki dvomi o odločitvi o usmerjanju, to storila tedne pozneje, pri čemer je bila datoteka morda medtem že zamenjana ali spremenjena.
Ko si mora operater ogledati datoteko v karanteni, je ne predajte privzetemu pregledovalniku sistema. Izrišite jo znotraj zaščitenega področja z onemogočenim izvajanjem skript in povezav, kot je opisano v članku o izdelavi varne površine za predogled PDF v Delphiju. Če vaš sprejem polni arhiv z zahtevami po skladnosti, je triažni prehod primerno mesto za načrtovanje globljega preverjanja; paketno preverjanje pred poletom (preflight) glede na profila PDF/A in PDF/UA se nadaljuje točno tam, kjer se ta pregled konča.
Predstavitvena stran komponente pokriva licenciranje, celoten API za pregledovanje in priložene predstavitvene programe (demos), vključno s pregledovalnikom prejetih dokumentov: PDFium Component.