Pracoviště pro vstupní kontrolu PDF je malý program s jedním úkolem: podívat se na každý soubor dřív, než se ho smí dotknout cokoli po proudu. Aby tento úkol splnil, musí do jednoho průchodu sestavit hrstku schopností. Otevře soubor (aniž by mu důvěřoval), přečte, co soubor sám o sobě tvrdí, hledá obsah, který zmate naivní extraktor nebo nese útok, rozhodne, jestli je v něm vůbec nějaký extrahovatelný text, a pak dokument nasměruje do fronty podle toho, co v něm našel. Vynechte kontrolu a selhání budou tichá: PDF šifrované heslem vlastníka, které v sobě balí formulář XFA, projede textovým extraktorem jako prázdné řetězce, zaindexuje se jako prázdný dokument, a nikdo si toho nevšimne, dokud někdo po proudu nebude hledat obsah, který nikdy nebyl přečten. PDFium Component je zdrojová knihovna VCL/LCL pro prohlížení a inspekci pro Delphi, C++Builder a Lazarus, a vystavuje introspekční volání, která toto pracoviště potřebuje. Následující sekce procházejí tím, které volání odpovídá na kterou otázku, a dvě místa, kde vám zjevné volání dá sebejistě špatnou odpověď
Pět otázek, na které je třeba odpovědět, než je soubor směrován
Odstraňte mřížku a pruh náhledů, a vstupní třídění se zredukuje na pět otázek:
- Dá se soubor vůbec otevřít, a pod jakým heslem?
- Za co se sám vydává: název, autor, datum vzniku?
- Nese aktivní nebo rizikový obsah, jako je JavaScript, formulář XFA nebo vložené soubory?
- Je v něm extrahovatelný text, nebo je to sken mířící na OCR?
- S ohledem na to vše, do které fronty patří: přímé zpracování, ruční kontrola, nebo karanténa?
Každá otázka se mapuje na jedno nebo dvě volání PDFium Component. Dvě z těchto mapování mají ostré hrany, které odpovídají za většinu špatně nasměrovaných souborů, které jsem musel v produkci ladit. Metadata dokumentu žijí na dvou různých místech, která si mohou odporovat, a šifrování nemusí dokumentu nutně zabránit v otevření
Otevřete levně: vyplňování formulářů vypnuté, žádná stránka vykreslená
Třídění by mělo být nejlevnějším možným otevřením. Nastavení FormFill := False před Active := True řekne komponentě, ať prostředí pro vyplňování formulářů úplně přeskočí. To zkrátí čas načítání, a (stejně důležité u souborů neznámého původu) to zabrání inicializaci jakéhokoli JavaScriptu na úrovni dokumentu. Žádná z inspekčních vlastností použitých níže nevyžaduje vykreslení stránky, takže třídicí průchod nikdy nemusí vyprodukovat jedinou bitmapu
procedure InspectIncoming(const IncomingPath: string; var Rec: TIntakeRecord);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := IncomingPath;
Pdf.FormFill := False; // žádné prostředí formuláře, žádná inicializace JavaScriptu
Pdf.Active := True; // selhání je tiché: Active prostě zůstane False
if not Pdf.Active then
begin
Rec.OpenFailed := True; // poškozený soubor nebo zámek uživatelským heslem
Exit; // blok finally se pořád spustí
end;
Rec.PageCount := Pdf.PageCount;
CollectIdentity(Pdf, IncomingPath, Rec);
CollectRiskSignals(Pdf, Rec);
finally
Pdf.Active := False;
Pdf.Free; // nikdy neuniknout instanci u poškozeného souboru
end;
end;
Kontrola za přiřazením není volitelná, a je to kontrola, ne obsluha výjimky, z konkrétního důvodu. Když engine nedokáže soubor načíst, komponenta pohltí interní EPdfError a nechá Active na False místo toho, aby ji propagovala dál. Kód, který čeká na výjimku, si vesele přečte PageCount z dokumentu, který se nikdy neotevřel. Pokud workflow pro odmítnutí potřebuje skutečný chybový text enginu, přečtěte soubor do pole bajtů a zavolejte přetížení LoadDocument, které bere TBytes; tato cesta EPdfError se zprávou skutečně vyvolá, včetně případu s heslem. try..finally si pořád zaslouží své místo. Vstupní služby běží bez dozoru celé týdny, a žádná pozdější výjimka nesmí uniknout instanci TPdf ani podržet zámek, o který pak zakopne opakovaný pokus
Propustnost se málokdy stane úzkým hrdlem. S vypnutým vyplňováním formulářů a bez vykreslování třídicímu otevření dominuje I/O, a jeden worker pohodlně prověří několik souborů za vteřinu z lokálního disku. Pokud objem vstupu jednou přerostě jednoho workera, rozdělte práci podle souboru, ne podle kontroly. Pět otázek sdílí jedno otevření, a jejich rozdělení napříč procesy by znásobilo nejdražší krok místo toho, aby ho amortizovalo
Metadata žijí na dvou místech, a odporují si
ISO 32000-1 definuje dva domovy pro metadata dokumentu: slovník informací o dokumentu (klauzule 14.3.3) a paket XMP připojený ke katalogu (klauzule 14.3.2). Vlastnosti Title, Author, Subject a CreationDate čtou Info dictionary, s MetaText[] pro jakýkoli jiný klíč a DecodeDate pro parsování datového řetězce D:YYYYMMDD.... Háček je v tom, že moderní producenti stále víc zapisují jen XMP, směr, který ISO 32000-2 zoficiálňuje tím, že v PDF 2.0 zavrhuje většinu klíčů Info dictionary. Příznak v nástroji pro vstupní kontrolu je konkrétní. Vaše pracoviště ukáže prázdný název, zatímco Adobe Acrobat nějaký zobrazí, protože Acrobat spadl zpátky na dc:title uvnitř paketu XMP, kterého se vlastnosti Info dictionary nikdy nedotknou
procedure CollectIdentity(Pdf: TPdf; const FilePath: string;
var Rec: TIntakeRecord);
begin
Rec.Title := Pdf.Title; // hodnota z Info dictionary
Rec.Author := Pdf.Author;
Rec.CreatedAt := Pdf.CreationDate; // syrový datový řetězec PDF („D:2026...")
// Prázdný title v Info neznamená, že dokument nemá název.
// Komponenta nevystavuje paket XMP, takže syrové bajty souboru
// prohledejte na element dc:title, než uvěříte prázdné hodnotě.
if (Rec.Title = '') and FileContainsText(FilePath, 'dc:title') then
Include(Rec.Flags, ifTitleInXmpOnly);
end;
I ta hrubá sonda na podřetězec výše si zaslouží své místo: „metadata jsou přítomná, ale ne tam, kam se dívají starší nástroje" je fakt relevantní pro směrování v jakémkoli archivním pipeline, který indexuje podle názvu nebo autora. Pokud váš navazující index čte jen Info dictionary, soubory takto označené se potichu stanou nevyhledatelnými
Šifrované soubory, které se stejně otevřou
Šifrovaný dokument se nutně neotevře neúspěšně. Standardní bezpečnostní handler (ISO 32000-1 klauzule 7.6.3) rozlišuje uživatelské heslo, vyžadované k otevření dokumentu, od hesla vlastníka, které jen brání oprávněním, jako je tisk a kopírování. Velký podíl „chráněných" firemních dokumentů je zašifrován heslem vlastníka a prázdným uživatelským heslem. Otevřou se bez výzvy, plně se dešifrují a spoléhají na to, že prohlížečky dobrovolně budou respektovat příznaky oprávnění. To je politika, ne ochrana, a vaše vstupní stavy by měly tento rozdíl odrážet
Detekce šifrování po úspěšném otevření zabere jedno volání enginu plus fallback. FPDF_GetSecurityHandlerRevision(Pdf.Document) vrací -1 pro nechráněné soubory a jinak revizi handleru, a Pdf.Permissions vracející cokoli jiného než masku se všemi bity nastavenými $FFFFFFFF je potvrzující signál. U opravdu heslem uživatele uzamčených souborů přiřaďte Password před nastavením Active := True; pokud otevření pořád selže, nasměrujte soubor do blokovaného stavu, který si vyžádá přihlašovací údaje od odesílatele přes bezpečný kanál, místo aby to slepě zkoušel znovu. A odolejte pokušení zacházet s „šifrováno" jako s automatickou karanténou. Ve většině odvětví náročných na dokumenty jsou šifrované, ale otevíratelné soubory běžný případ, ne podezřelý
Aktivní obsah: JavaScript, XFA a vložené soubory
Tři nálezy by měly vždy dorazit až k rozhodnutí o směrování. Za prvé, JavaScript: událost OnUnsupportedFeature hlásí strukturální funkce, jako XFA nebo 3D obsah, ve chvíli, kdy na ně engine narazí, ale JavaScript nedetekuje. Místo toho zkontrolujte JavaScriptActionCount a nenulový výsledek berte jako aktivní obsah. Za druhé, XFA: když FormType vrátí ftXfaFull, viditelné stránky jsou často málo víc než vykreslení šablony XFA, a konvenční extrakce textu uvidí obecnou šablonu místo vyplněných hodnot. Za třetí, přílohy: PDF je kontejnerový formát, a AttachmentCount vám řekne, jestli tenhle konkrétní soubor veze cestující
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 je vlastnost po stránkách; projít stránky a sečíst
// je. Načtení objektu stránky nic nevykresluje, takže to zůstává levné.
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;
Dva detaily v této smyčce si zaslouží pozornost. Název přílohy pochází zevnitř dokumentu, takže ho nikdy nepoužívejte jako výstupní cestu bez předchozí sanitizace; vložený název jako ..\..\start.exe je path traversal čekající na neopatrné volání uložení. A blocklist přípon je jen tripwire, ne záruka. Jeho úkolem je vynutit lidské rozhodnutí, ne osvědčit, že je soubor čistý
Přeměna signálů na směrovací stavy
Funkční model stavů potřebuje méně stavů, než většina týmů čeká: ready (žádné blokátory, text přítomen), review (otevření uspělo, ale něco potřebuje lidské oči, jako formulář XFA, JavaScript, prázdná textová vrstva nebo název jen v XMP), blocked (vyžadováno uživatelské heslo) a damaged (otevření selhalo). Zaznamenávejte důkazy spolu se stavem. Hash souboru, počet stránek, přesné příznaky a chybová zpráva enginu u poškozených souborů, to vše záleží, protože člověk, který rozhodnutí o směrování zpochybní, to udělá o týdny později, proti souboru, který mezitím mohl být nahrazen nebo upraven
Když si operátor opravdu potřebuje prohlédnout soubor v karanténě, nedávejte mu ho do výchozí prohlížečky shellu. Vykreslete ho uvnitř zpevněného panelu s vypnutým skriptováním a zpracováním odkazů, přístup popsaný v budování bezpečného náhledového povrchu PDF v Delphi. A pokud vaše vstupní kontrola krmí archiv s požadavky na shodu, třídicí průchod je přirozené místo, kam naplánovat hlubší kontrolu; dávková preflight validace proti profilům PDF/A a PDF/UA navazuje přesně tam, kde tato inspekce končí
Produktová stránka komponenty pokrývá licencování, plné inspekční API a přibalená dema, včetně inspektoru dokumentů ve stylu vstupní kontroly: PDFium Component