Buka PDF yang dihasilkan Microsoft Word atau Excel, buka halaman-halamannya, dan tidak ada yang terlihat aneh. Muat ke dalam program Delphi, baca kembali jumlah halamannya, dan angkanya benar. Kemudian simpan kembali dengan enkripsi diaktifkan dan pekerjaan gagal dengan EListError, atau output terbuka dengan peringatan referensi silang yang rusak. File tersebut sebenarnya tidak pernah rusak. Ini adalah file referensi hibrida (hybrid-reference), dan struktur yang memungkinkan penampil berusia lima belas tahun membukanya adalah struktur yang sama yang menggagalkan pemuat (loader) yang berhenti membaca terlalu cepat.
Ini adalah salah satu cara paling umum di mana alur kerja PDF yang lolos setiap pengujian internal menemui file yang tidak dapat diproses pulang-pergi (round-trip). Input semuanya dihasilkan secara internal, sehingga tidak pernah hibrida. File hibrida pertama tiba saat pelanggan meneruskan faktur yang diekspor dari spreadsheet.
Apa yang sebenarnya ditulis oleh Word dan Excel
ISO 32000-1 menjelaskan tata letak referensi hibrida dalam §7.5.8.4. Aplikasi yang menginginkan fitur PDF 1.5 seperti stream objek, sambil tetap membiarkan pembaca PDF 1.4 membuka file, menulis informasi referensi silang dua kali. Ada tabel referensi silang klasik, baris ASCII dengan lebar tetap yang mengakhiri setiap PDF hingga versi 1.4, dan ada stream referensi silang yang mengindeks sisanya. Trailer dari bagian klasik membawa entri /XRefStm yang nilainya adalah offset byte dari stream tersebut.
Pembagian tugas ini disengaja. Objek yang harus dijangkau oleh pembaca lama, di antaranya katalog dan pohon halaman, dapat diakses dari tabel klasik. Objek yang dilipat ke dalam stream objek terkompresi ditandai sebagai bebas (free) di tabel klasik, dengan entri tipe f, sehingga pembaca 1.4 langsung melewatinya dan tidak pernah tersandung pada struktur yang tidak dapat diparansinya. Lokasi sebenarnya hanya ada di stream referensi silang. Ciri khas file semacam itu adalah bagian akhirnya: bagian klasik pendek, sering kali tidak lebih dari xref diikuti oleh header subbagian 0 0, yang trailernya mengarah ke /XRefStm tempat data pemulihan sebenarnya berada.
Mengapa jumlah halaman yang benar tidak membuktikan apa-apa
Karena katalog dan pohon halaman sengaja dibuat agar dapat dijangkau dari tabel klasik, pemuat (loader) yang hanya membaca tabel tersebut akan menemukan /Root, menelusuri pohon halaman, dan melaporkan jumlah halaman yang benar. Semua yang dibutuhkan pembaca lama ada, sehingga file tampak sehat. Objek yang hilang adalah objek yang dikemas ke dalam stream objek: kamus bidang AcroForm, elemen struktur tagged-PDF, dan ekor panjang kamus kecil yang tidak perlu terlihat oleh pemirsa warisan (legacy).
Anda tidak akan menyadari celah tersebut sampai ada sesuatu yang menyentuh objek-objek itu, dan penyimpanan ulang penuh (full resave) menyentuh semuanya. Menelusuri dokumen untuk mengenkripsi ulang atau menulis ulang adalah operasi yang meminta setiap nomor objek secara bergantian, itulah sebabnya gejalanya muncul pada waktu penyimpanan daripada waktu pemuatan, jauh dari penyebabnya.
Jebakannya adalah detektor yang melihat xref lalu berhenti
Cara murah untuk memutuskan bagaimana suatu file diindeks adalah dengan mengikuti startxref and memeriksa byte pertama yang ditunjuknya. Kata kunci xref berarti tabel klasik; objek stream berarti stream referensi silang. Pengujian tersebut benar untuk file apa pun yang berkomitmen pada satu skema. Ini salah untuk file hibrida, di mana startxref-nya ditujukan pada bagian klasik untuk tujuan memuaskan pembaca lama, sedangkan /XRefStm di trailer bagian tersebut adalah tempat sebagian besar dokumen sebenarnya diindeks. Detektor yang mengembalikan "klasik" pada xref pertama yang ditemuinya tidak akan pernah membaca /XRefStm, dan setiap objek yang hanya ada di dalam stream menjadi tidak terlihat.
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf'); // count is correct
// inspect or edit the loaded document here
Pdf.SaveLoadedDocument('Invoice_secured.pdf'); // walks every object
finally
Pdf.Free;
end;
end;
Dengan adanya detektor keluar-cepat (early-exit), pemuatan terlihat baik-baik saja dan penyimpanan ulang adalah tempat di mana objek yang tidak ada mengumumkan diri mereka sendiri. Perbaikannya bukanlah membaca lebih banyak byte di awal; melainkan mengenali trailer hibrida dan mengikuti /XRefStm sebelum memutuskan bahwa file telah selesai diproses.
Urutan penggabungan tidak dapat ditawar
Setelah kedua indeks dibaca, keduanya hanya dapat digabungkan dalam satu arah saja. Stream referensi silang harus digabungkan terlebih dahulu, dengan entri klasik diisi di sekitarnya. Alasannya adalah tipu daya kecil di jantung format ini. File hibrida menandai objek terkompresinya sebagai bebas di tabel klasik sehingga pembaca lama mengabaikannya. Pemuat (loader) yang menghormati kebijakan "yang pertama kali terlihat menang" (first-seen-wins) dan membaca tabel klasik terlebih dahulu akan mencatat nomor objek tersebut sebagai bebas, kemudian membuang entri stream yang sebenarnya menempatkannya, karena slot tersebut sudah terisi. Balikkan urutannya, maka entri tipe 2 dari stream, masing-masing berupa nomor stream objek ditambah indeks, memenangkan slot yang seharusnya mereka miliki, dan entri klasik menyesuaikan di sekitar mereka.
Disiplin yang sama menjaga dari revisi lama yang menghidupkan kembali objek yang dihapus. Pembaruan inkremental berantai mundur melalui /Prev, dan entri bebas tipe 0 adalah penjaga (sentinel) bahwa bagian yang lebih baru telah menghentikan penggunaan nomor objek. Bagian lama yang muncul kemudian dalam rantai tidak boleh diizinkan untuk menimpa sentinel tersebut dengan lokasi yang usang. Perlakukan entri pertama kali terlihat (first-seen) sebagai otoritatif untuk penanda bebas dan objek yang dihapus tetap terhapus; perlakukan dengan ceroboh maka sejarah file itu sendiri akan menghidupkan kembali konten yang telah dihapus oleh revisi terbaru.
Apa artinya ini bagi HotPDF
Mesin menyelesaikan file referensi hibrida untuk Anda, dan melakukannya pada setiap jalur yang harus mengurai data referensi silang. Muat dokumen dengan LoadFromFile or LoadFromStream, buat perubahan Anda, dan panggil SaveLoadedDocument; atau jalankan operasi satu kali (one-shot) seperti EncryptFile yang membaca input dan menulis output. Dengan cara apa pun, pemulihan membaca /XRefStm, menggabungkan bagian stream sebelum entri klasik, dan menyelesaikan objek yang berada di dalam stream sebelum penulisan menghitungnya. Jalur enkripsi AES-256 adalah tempat masalah pertama kali menunjukkan dirinya, karena mengenkripsi dokumen menulis ulang setiap objek dan menuntut setiap objek telah ditemukan lokasinya.
// One-shot: read the hybrid input, write an AES-256 encrypted copy
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
'owner-secret', '', aes256, [prPrint, prFillAnnotations]);
Detail yang layak diingat berada di hulu API. File yang berasal dari Word, Excel, PowerPoint, dan daftar panjang alur kerja "Save as PDF" biasanya bersifat hibrida, sehingga pemuat (loader) yang hanya Anda uji terhadap output generator Anda sendiri mungkin tidak pernah menemukannya dalam pengujian. Masukkan dokumen yang diekspor dari aplikasi Office nyata ke dalam lingkungan pengujian Anda, tidak hanya file yang dihasilkan oleh kode Anda sendiri.
Memeriksa file yang Anda curigai
Dua inspeksi menyelesaikan pertanyaan ini dengan cepat. Buka file dalam tampilan hex dan baca byte setelah startxref terakhir; file hibrida menunjukkan bagian klasik pendek yang kamus trailer-nya berisi /XRefStm. Or bandingkan jumlah objek yang dilaporkan oleh penguraian penuh dengan nomor objek tertinggi yang dideklarasikan oleh /Size di trailer. Kesenjangan yang besar berarti ada objek yang bersembunyi dalam stream yang belum dibuka oleh loader, yang merupakan kekurangan yang sama yang berubah menjadi kegagalan pada waktu penyimpanan nantinya.
Sisi penulis dari cerita ini, bagaimana stream objek dan referensi silang terkompresi diproduksi di tempat pertama, dibahas dalam artikel kami tentang stream objek dan pembaruan inkremental. Ketika file hibrida yang dimaksud juga sangat besar, teknik pemuatan dalam panduan Direct File API untuk alur kerja PDF besar memungkinkan Anda memeriksanya tanpa membaca semuanya ke dalam memori. Keduanya berpasangan secara alami dengan pemulihan yang dijelaskan di sini, yang dikirimkan sebagai bagian dari HotPDF Component untuk Delphi dan C++Builder bersama dengan API pemuatan, pengeditan, enkripsi, dan penandatanganan yang dibahas di bagian lain di blog ini.