En arbejdsstation til gennemgang af indkomne PDF'er er et lille program med én opgave: se på hver eneste fil, før noget efterfølgende får lov til at røre den. For at udføre den opgave skal den samle en håndfuld egenskaber i ét gennemløb. Den åbner filen (uden at stole på den), læser hvad filen selv hævder om sig selv, leder efter indhold, der vil vildlede en naiv udtrækker eller bære et angreb, afgør om der overhovedet findes udtrækkelig tekst, og dirigerer derefter dokumentet til en kø baseret på det, den fandt. Springer man inspektionen over, bliver fejlene stille: en PDF krypteret med ejeradgangskode, der pakker en XFA-formular ind, glider gennem en tekstudtrækker som tomme strenge, bliver indekseret som et blankt dokument, og ingen bemærker det, før nogen efterfølgende går på jagt efter indhold, der aldrig blev læst. PDFium Component er et kildekodebaseret VCL/LCL-viewer- og inspektionsbibliotek til Delphi, C++Builder og Lazarus, og det eksponerer de introspektionskald, denne arbejdsstation har brug for. Afsnittene nedenfor gennemgår, hvilket kald der besvarer hvilket spørgsmål, og de to steder, hvor det oplagte kald giver dig et sikkert forkert svar
Fem spørgsmål, der skal besvares, før en fil dirigeres
Fjern gitteret og miniaturestrimlen, og indtagstriagen reduceres til fem spørgsmål:
- Kan filen overhovedet åbnes, og under hvilken adgangskode?
- Hvad hævder den at være: titel, forfatter, oprettelsesdato?
- Bærer den aktivt eller risikabelt indhold såsom JavaScript, en XFA-formular eller indlejrede filer?
- Findes der udtrækkelig tekst, eller er det en scanning på vej til OCR?
- I betragtning af alt det, hvilken kø får den: direkte gennemløb, manuel gennemgang eller karantæne?
Hvert spørgsmål svarer til ét eller to PDFium Component-kald. To af de sammenhænge har skarpe hjørner, som står for de fleste af de fejldirigerede filer, jeg har måttet fejlsøge i produktion. Dokumentmetadata findes to forskellige steder, som kan være uenige, og kryptering forhindrer ikke nødvendigvis et dokument i at blive åbnet
Åbn billigt: formularudfyldning slået fra, nul sider rendret
Triage bør være den billigst mulige åbning. At sætte FormFill := False før Active := True fortæller komponenten, at den helt skal springe formularudfyldningsmiljøet over. Det forkorter indlæsningstiden, og (lige så vigtigt for filer af ukendt oprindelse) det forhindrer al JavaScript på dokumentniveau i at blive initieret. Ingen af de inspektionsegenskaber, der bruges nedenfor, kræver rendering af en side, så et triage-gennemløb behøver aldrig producere en eneste bitmap
procedure InspectIncoming(const IncomingPath: string; var Rec: TIntakeRecord);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := IncomingPath;
Pdf.FormFill := False; // intet formularmiljø, ingen JavaScript-initiering
Pdf.Active := True; // fejl er stille: Active forbliver blot False
if not Pdf.Active then
begin
Rec.OpenFailed := True; // beskadiget fil eller brugeradgangskode-lås
Exit; // finally-blokken kører stadig
end;
Rec.PageCount := Pdf.PageCount;
CollectIdentity(Pdf, IncomingPath, Rec);
CollectRiskSignals(Pdf, Rec);
finally
Pdf.Active := False;
Pdf.Free; // lad aldrig instansen lække ved en fejlformet fil
end;
end;
Kontrollen efter tildelingen er ikke valgfri, og det er en kontrol snarere end en exception-håndtering af en grund. Når motoren ikke kan indlæse filen, sluger komponenten den interne EPdfError og lader Active forblive False i stedet for at videregive den. Kode, der venter på en exception, vil villigt læse PageCount fra et dokument, der aldrig blev åbnet. Hvis afvisningsarbejdsgangen har brug for motorens faktiske fejltekst, skal man læse filen ind i et byte-array og kalde LoadDocument-overloadet, der tager TBytes; den sti udløser rent faktisk EPdfError med beskeden, inklusive adgangskode-tilfældet. try..finally fortjener stadig sin plads. Indtagstjenester kører uovervåget i flere uger, og ingen senere exception må lade TPdf-instansen lække eller holde en lås, som genforsøgstrinnet vil snuble over
Gennemløb bliver sjældent flaskehalsen. Med formularudfyldning slået fra og ingen rendering domineres en triage-åbning af I/O, og én enkelt arbejder inspicerer uden problemer flere filer i sekundet fra lokal disk. Hvis indtagsvolumen nogensinde vokser fra én arbejder, skal arbejdet partitioneres efter fil snarere end efter kontrol. De fem spørgsmål deler én åbning, og at splitte dem over flere processer ville mangedoble det dyreste trin i stedet for at amortisere det
Metadata findes to steder, og de er uenige
ISO 32000-1 definerer to hjemsteder for dokumentmetadata: dokumentets informations-dictionary (afsnit 14.3.3) og en XMP-pakke knyttet til kataloget (afsnit 14.3.2). Egenskaberne Title, Author, Subject og CreationDate læser Info-dictionaryet, med MetaText[] til enhver anden nøgle og DecodeDate til at fortolke D:YYYYMMDD...-datostrengen. Faldgruben er, at moderne producenter i stigende grad kun skriver XMP, en retning som ISO 32000-2 gør officiel ved at udfase de fleste Info-dictionary-nøgler i PDF 2.0. Symptomet i et indtagsværktøj er konkret. Din arbejdsstation viser en tom titel, mens Adobe Acrobat viser en, fordi Acrobat faldt tilbage på dc:title inde i XMP-pakken, som Info-dictionary-egenskaberne aldrig rører
procedure CollectIdentity(Pdf: TPdf; const FilePath: string;
var Rec: TIntakeRecord);
begin
Rec.Title := Pdf.Title; // værdi fra Info-dictionary
Rec.Author := Pdf.Author;
Rec.CreatedAt := Pdf.CreationDate; // rå PDF-datostreng ("D:2026...")
// En tom Info-titel betyder ikke, at dokumentet er uden titel. Komponenten
// eksponerer ikke XMP-pakken, så undersøg de rå filbytes for
// dc:title-elementet, før den tomme værdi antages at være retvisende.
if (Rec.Title = '') and FileContainsText(FilePath, 'dc:title') then
Include(Rec.Flags, ifTitleInXmpOnly);
end;
Selv den grove substreng-undersøgelse ovenfor fortjener sin plads: "metadata til stede, men ikke dér, hvor ældre værktøjer kigger" er en dirigeringsrelevant kendsgerning for enhver arkivpipeline, der indekserer på titel eller forfatter. Hvis dit efterfølgende indeks kun læser Info-dictionaryet, bliver filer markeret på denne måde i det stille usøgbare
Krypterede filer, der alligevel åbner
Et krypteret dokument fejler ikke nødvendigvis ved åbning. Standardsikkerhedshåndteringen (ISO 32000-1 afsnit 7.6.3) skelner mellem en brugeradgangskode, som kræves for at åbne dokumentet, og en ejeradgangskode, der blot regulerer tilladelser såsom udskrivning og kopiering. En stor andel af "beskyttede" forretningsdokumenter er krypteret med en ejeradgangskode og en tom brugeradgangskode. De åbner uden at spørge, dekrypterer fuldt ud og er afhængige af, at viewere frivilligt overholder tilladelsesflagene. Det er politik, ikke beskyttelse, og dine indtagstilstande bør afspejle forskellen
At opdage kryptering efter en vellykket åbning kræver ét motorkald plus en reserveløsning. FPDF_GetSecurityHandlerRevision(Pdf.Document) returnerer -1 for ubeskyttede filer og ellers håndteringsrevisionen, og at Pdf.Permissions returnerer noget andet end masken med alle bits sat, $FFFFFFFF, er det understøttende signal. For filer, der reelt er låst med brugeradgangskode, tildeles Password, før Active := True sættes; hvis åbningen stadig fejler, dirigeres filen til en blokeret tilstand, der anmoder om legitimationsoplysninger fra afsenderen gennem en sikker kanal i stedet for blindt at forsøge igen. Og modstå fristelsen til at behandle "krypteret" som automatisk karantæne. I de fleste dokumenttunge brancher er krypterede-men-åbnelige filer normaltilfældet, ikke det mistænkelige
Aktivt indhold: JavaScript, XFA og indlejrede filer
Tre fund bør altid nå frem til dirigeringsbeslutningen. For det første JavaScript: OnUnsupportedFeature-hændelsen rapporterer strukturelle egenskaber såsom XFA eller 3D-indhold, efterhånden som motoren støder på dem, men den registrerer ikke JavaScript. Kontrollér i stedet JavaScriptActionCount, og behandl et resultat forskelligt fra nul som aktivt indhold. For det andet XFA: når FormType returnerer ftXfaFull, er de synlige sider ofte lidt mere end en rendering af XFA-skabelonen, og konventionel tekstudtrækning vil se standardtekst i stedet for de udfyldte værdier. For det tredje vedhæftninger: en PDF er et containerformat, og AttachmentCount fortæller dig, om denne ene har passagerer med
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 er en egenskab pr. side; gennemløb siderne for at
// summere den. Indlæsning af et sideobjekt rendrer intet, så det forbliver billigt.
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;
To detaljer i den løkke fortjener opmærksomhed. Vedhæftningsnavnet kommer fra inde i dokumentet, så genbrug det aldrig som en outputsti uden først at sanere det; et indlejret navn som ..\..\start.exe er en stitraversering, der venter på et skødesløst gem-kald. Og en udvidelses-blokliste er en snublesnor, ikke en garanti. Dens job er at fremtvinge en menneskelig beslutning, ikke at attestere, at filen er ren
At omsætte signaler til dirigeringstilstande
En brugbar tilstandsmodel kræver færre tilstande, end de fleste teams forventer: klar (ingen blokeringer, tekst til stede), gennemgang (åbning lykkedes, men noget kræver et blik, såsom en XFA-formular, JavaScript, et tomt tekstlag eller en titel kun i XMP), blokeret (brugeradgangskode påkrævet), og beskadiget (åbning fejlede). Registrér beviserne sammen med tilstanden. Filens hash, sidetal, de præcise flag og motorens fejlmeddelelse for beskadigede filer betyder alt sammen noget, fordi den person, der senere sætter spørgsmålstegn ved en dirigeringsbeslutning, vil gøre det uger senere, over for en fil, der i mellemtiden kan være blevet erstattet eller ændret
Når en operatør rent faktisk har brug for at se på en karantænefil, må den ikke overgives til standard-shell-viewer. Render den inden i en hærdet rude med scripting og linkhåndtering slået fra, den fremgangsmåde, der er beskrevet i opbygning af en sikker PDF-forhåndsvisningsflade i Delphi. Og hvis dit indtag fodrer et arkiv med overensstemmelseskrav, er triage-gennemløbet det naturlige sted at planlægge en dybere kontrol; batch-preflight-validering mod PDF/A- og PDF/UA-profiler tager over præcis dér, hvor denne inspektion stopper
Komponentens produktside dækker licensering, det fulde inspektions-API og de medfølgende demoer, herunder en dokumentinspektør i indtagsstil: PDFium Component