Sebuah PDF intake review workbench adalah program kecil dengan satu tugas: memeriksa setiap file sebelum apa pun di hilir diizinkan menyentuhnya. Untuk melakukan tugas itu, ia harus merangkai sejumlah kemampuan menjadi satu pass. Ia membuka file (tanpa mempercayainya), membaca apa yang diklaim oleh file itu tentang dirinya sendiri, mencari konten yang akan menyesatkan sebuah extractor yang naif atau membawa sebuah serangan, memutuskan apakah ada teks yang dapat diekstrak sama sekali, dan kemudian merutekan dokumen itu ke sebuah antrean berdasarkan apa yang ditemukannya. Lewati inspeksi ini dan kegagalannya akan senyap: sebuah PDF yang dienkripsi dengan owner-password yang membungkus sebuah form XFA melenggang melewati sebuah text extractor sebagai string kosong, terindeks sebagai dokumen kosong, dan tidak ada yang menyadarinya sampai seseorang di hilir mencari konten yang tidak pernah terbaca. PDFium Component adalah pustaka viewer dan inspeksi VCL/LCL dengan source code untuk Delphi, C++Builder, dan Lazarus, dan ia mengekspos panggilan introspeksi yang dibutuhkan workbench ini. Bagian-bagian di bawah ini menelusuri panggilan mana yang menjawab pertanyaan mana, dan dua tempat di mana panggilan yang tampak jelas justru memberi Anda jawaban yang keliru dengan penuh percaya diri
Lima pertanyaan yang harus dijawab sebelum sebuah file dirutekan
Singkirkan grid dan strip thumbnail-nya, dan triase intake berkurang menjadi lima pertanyaan:
- Dapatkah file ini dibuka sama sekali, dan dengan password yang mana?
- Apa yang diklaimnya sebagai dirinya: judul, penulis, tanggal pembuatan?
- Apakah ia membawa konten aktif atau berisiko seperti JavaScript, sebuah form XFA, atau file yang disematkan?
- Apakah ada teks yang dapat diekstrak, atau ini adalah sebuah hasil pindai yang ditujukan untuk OCR?
- Dengan semua itu, antrean mana yang menerimanya: pemrosesan langsung, peninjauan manual, atau karantina?
Setiap pertanyaan dipetakan ke satu atau dua panggilan PDFium Component. Dua dari pemetaan itu memiliki sudut tajam yang menjadi penyebab sebagian besar file yang salah rute yang pernah harus saya debug di produksi. Metadata dokumen tinggal di dua tempat berbeda yang bisa saling tidak sepakat, dan enkripsi tidak selalu menghentikan sebuah dokumen untuk dibuka
Buka dengan murah: form fill mati, nol halaman dirender
Triase seharusnya menjadi pembukaan yang semurah mungkin. Menetapkan FormFill := False sebelum Active := True memberi tahu komponen untuk melewati lingkungan form-fill sepenuhnya. Itu mempersingkat waktu muat, dan (sama pentingnya untuk file yang asal-usulnya tidak diketahui) itu mencegah JavaScript level-dokumen apa pun untuk diinisialisasi. Tidak satu pun dari properti inspeksi yang digunakan di bawah ini membutuhkan rendering sebuah halaman, sehingga sebuah pass triase tidak pernah perlu memproduksi satu bitmap pun
procedure InspectIncoming(const IncomingPath: string; var Rec: TIntakeRecord);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := IncomingPath;
Pdf.FormFill := False; // tanpa lingkungan form, tanpa inisialisasi JavaScript
Pdf.Active := True; // kegagalan bersifat senyap: Active hanya tetap False
if not Pdf.Active then
begin
Rec.OpenFailed := True; // file rusak atau terkunci user-password
Exit; // blok finally tetap berjalan
end;
Rec.PageCount := Pdf.PageCount;
CollectIdentity(Pdf, IncomingPath, Rec);
CollectRiskSignals(Pdf, Rec);
finally
Pdf.Active := False;
Pdf.Free; // jangan pernah membocorkan instance pada file yang cacat
end;
end;
Pemeriksaan setelah penetapan itu bukanlah opsional, dan itu adalah sebuah pemeriksaan, bukan sebuah exception handler, dengan alasan tertentu. Ketika engine tidak dapat memuat file, komponen ini menelan EPdfError internal dan membiarkan Active tetap False, alih-alih menyebarkannya. Kode yang menunggu sebuah exception akan dengan senang hati membaca PageCount dari sebuah dokumen yang tidak pernah terbuka. Jika alur kerja penolakan membutuhkan teks error asli milik engine, baca file itu ke dalam sebuah array byte dan panggil overload LoadDocument yang menerima TBytes; jalur itu memang memicu EPdfError beserta pesannya, termasuk pada kasus password. try..finally tetap mendapat tempatnya. Layanan intake berjalan tanpa pengawasan selama berminggu-minggu, dan tidak boleh ada exception belakangan yang membocorkan instance TPdf atau menahan sebuah lock yang akan tersandung oleh pass percobaan ulang
Throughput jarang menjadi bottleneck. Dengan form fill dinonaktifkan dan tanpa rendering, sebuah pembukaan triase didominasi oleh I/O, dan satu worker tunggal dapat dengan nyaman memeriksa beberapa file per detik dari disk lokal. Jika volume intake pernah melampaui satu worker, bagilah pekerjaan berdasarkan file, bukan berdasarkan pemeriksaan. Kelima pertanyaan itu berbagi satu pembukaan, dan memecahnya ke berbagai proses justru akan melipatgandakan langkah yang paling mahal, alih-alih mengamortisasinya
Metadata tinggal di dua tempat, dan keduanya tidak sepakat
ISO 32000-1 mendefinisikan dua tempat tinggal bagi metadata dokumen: document information dictionary (klausul 14.3.3) dan sebuah paket XMP yang dilekatkan pada catalog (klausul 14.3.2). Properti Title, Author, Subject, dan CreationDate membaca Info dictionary, dengan MetaText[] untuk key lain apa pun dan DecodeDate untuk mengurai string tanggal D:YYYYMMDD.... Ganjalannya adalah producer modern semakin sering hanya menulis XMP saja, sebuah arah yang diresmikan oleh ISO 32000-2 dengan men-deprecated sebagian besar key Info dictionary di PDF 2.0. Gejalanya dalam sebuah tool intake bersifat konkret. Workbench Anda menampilkan judul kosong sementara Adobe Acrobat menampilkan satu judul, karena Acrobat jatuh kembali ke dc:title di dalam paket XMP, yang tidak pernah disentuh oleh properti Info-dictionary
procedure CollectIdentity(Pdf: TPdf; const FilePath: string;
var Rec: TIntakeRecord);
begin
Rec.Title := Pdf.Title; // nilai dari Info dictionary
Rec.Author := Pdf.Author;
Rec.CreatedAt := Pdf.CreationDate; // string tanggal PDF mentah ("D:2026...")
// Judul Info yang kosong tidak berarti dokumen ini tidak berjudul. Komponen
// ini tidak mengekspos paket XMP, jadi periksa byte file mentah untuk
// elemen dc:title sebelum mempercayai kekosongan itu.
if (Rec.Title = '') and FileContainsText(FilePath, 'dc:title') then
Include(Rec.Flags, ifTitleInXmpOnly);
end;
Bahkan probe substring yang kasar di atas pun tetap berguna: "metadata ada, tetapi bukan di tempat yang dicari oleh tool lama" adalah sebuah fakta yang relevan untuk routing bagi pipeline arsip mana pun yang mengindeks berdasarkan judul atau penulis. Jika indeks hilir Anda hanya membaca Info dictionary, file yang ditandai dengan cara ini akan diam-diam menjadi tidak dapat dicari
File terenkripsi yang tetap dapat dibuka
Sebuah dokumen terenkripsi tidak selalu gagal dibuka. Standard security handler (ISO 32000-1 klausul 7.6.3) membedakan sebuah user password, yang diperlukan untuk membuka dokumen, dari sebuah owner password yang hanya membatasi izin seperti pencetakan dan penyalinan. Sebagian besar dokumen bisnis yang "terlindungi" dienkripsi dengan sebuah owner password dan sebuah user password yang kosong. Dokumen-dokumen itu terbuka tanpa meminta apa pun, terdekripsi sepenuhnya, dan mengandalkan viewer yang secara sukarela menghormati flag izin. Itu adalah kebijakan, bukan perlindungan, dan state intake Anda harus mencerminkan perbedaan itu
Mendeteksi enkripsi setelah sebuah pembukaan yang berhasil membutuhkan satu panggilan engine ditambah sebuah fallback. FPDF_GetSecurityHandlerRevision(Pdf.Document) mengembalikan -1 untuk file yang tidak terlindungi dan revisi handler-nya jika sebaliknya, dan Pdf.Permissions yang mengembalikan apa pun selain mask $FFFFFFFF yang semua bit-nya diset adalah sinyal pendukungnya. Untuk file yang benar-benar dikunci user-password, tetapkan Password sebelum menetapkan Active := True; jika pembukaannya masih gagal, rutekan file itu ke sebuah state terblokir yang meminta kredensial dari pengirim melalui sebuah saluran yang aman, alih-alih mencoba ulang secara membabi buta. Dan tahan godaan untuk memperlakukan "terenkripsi" sebagai karantina otomatis. Di sebagian besar industri yang padat dokumen, file yang terenkripsi tetapi tetap dapat dibuka adalah kasus normal, bukan kasus yang mencurigakan
Konten aktif: JavaScript, XFA, dan file yang disematkan
Tiga temuan harus selalu sampai ke keputusan routing. Pertama, JavaScript: event OnUnsupportedFeature melaporkan fitur struktural seperti XFA atau konten 3D saat engine menemukannya, tetapi ia tidak mendeteksi JavaScript. Periksa JavaScriptActionCount sebagai gantinya dan perlakukan hasil bukan-nol sebagai konten aktif. Kedua, XFA: ketika FormType mengembalikan ftXfaFull, halaman yang terlihat sering kali tidak lebih dari sekadar rendering templat XFA, dan ekstraksi teks konvensional akan melihat boilerplate, bukan nilai yang sudah diisi. Ketiga, lampiran: sebuah PDF adalah format kontainer, dan AttachmentCount memberi tahu Anda apakah file ini membawa penumpang
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 adalah properti per-halaman; telusuri semua halaman untuk
// menotalkannya. Memuat objek halaman tidak me-render apa pun, sehingga ini tetap murah.
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;
Dua detail dalam loop itu layak mendapat perhatian. Nama lampiran berasal dari dalam dokumen, jadi jangan pernah menggunakannya kembali sebagai output path tanpa disanitasi terlebih dahulu; sebuah nama yang disematkan seperti ..\..\start.exe adalah sebuah path traversal yang menunggu sebuah panggilan save yang ceroboh. Dan sebuah extension blocklist adalah sebuah tripwire, bukan sebuah jaminan. Tugasnya adalah memaksa sebuah keputusan manusia, bukan mensertifikasi file itu bersih
Mengubah sinyal menjadi state routing
Sebuah model state yang dapat digunakan membutuhkan state yang lebih sedikit daripada yang diharapkan kebanyakan tim: ready (tidak ada pemblokir, teks ada), review (pembukaan berhasil tetapi ada sesuatu yang butuh mata manusia, seperti sebuah form XFA, JavaScript, lapisan teks yang kosong, atau sebuah judul yang hanya ada di XMP), blocked (user password diperlukan), dan damaged (pembukaan gagal). Catat buktinya berdampingan dengan state-nya. Hash file, jumlah halaman, flag yang persis, dan pesan error engine untuk file yang rusak semuanya penting, karena orang yang mempertanyakan sebuah keputusan routing akan melakukannya berminggu-minggu kemudian, terhadap sebuah file yang mungkin sejak itu telah diganti atau dimodifikasi
Ketika seorang operator memang perlu melihat sebuah file yang dikarantina, jangan serahkan itu ke viewer shell default. Render file itu di dalam sebuah panel yang diperkeras dengan scripting dan penanganan link dinonaktifkan, pendekatan yang dijelaskan dalam membangun permukaan pratinjau PDF yang aman di Delphi. Dan jika intake Anda memasok sebuah arsip dengan persyaratan konformansi, pass triase ini adalah tempat yang alami untuk menjadwalkan sebuah pemeriksaan yang lebih dalam; validasi preflight batch terhadap profil PDF/A dan PDF/UA mengambil alih persis di tempat inspeksi ini berhenti
Halaman produk komponen ini membahas lisensi, inspection API lengkap, dan demo yang disertakan, termasuk sebuah document inspector bergaya intake: PDFium Component