PDFlibPas mendekode TIFF dengan parser Object Pascal yang ditulis sendiri, bukan binding libtiff, dan versi 3.534.1 memperketat tepat di titik-titik parser tersebut menolak input. Magic 43 BigTIFF kini ditolak dengan disebut namanya, TileOffsets dan TileByteCounts ditolak saat parsing tag, dan setiap buffer diukur lewat aritmetika Int64 di bawah plafon dekode 256 MiB
Cacat yang ditutup ini tidak pernah muncul di laboratorium. Ia muncul sebagai gateway pemindai yang berjalan sunyi selama tiga tahun, sampai seorang pelanggan mengarahkan arsip geospasial atau citra medis whole-slide ke dalamnya. Berkasnya memiliki header TIFF yang sah. Berkas itu terurai. Yang keluar adalah satu halaman noise bergaris, atau alokasi multigigabyte yang menjatuhkan layanan, dan tidak ada satu pun langkah di sepanjang jalan yang menyatakan input itu tidak valid. Itulah bentuk kegagalan yang patut diantisipasi secara rekayasa: bukan crash, melainkan jawaban yang salah yang disampaikan dengan percaya diri
Mengapa II atau MM tidak membuktikan Anda punya TIFF klasik?
Karena penanda byte-order dipakai bersama oleh kedua dialek. TIFF klasik dan BigTIFF sama-sama dibuka dengan II atau MM, dan field yang benar-benar membedakan keduanya adalah magic 16-bit tepat setelahnya: 42 untuk TIFF klasik sebagaimana didefinisikan dalam spesifikasi TIFF 6.0, 43 untuk BigTIFF dengan offset 64-bit-nya. Loader yang ditulis sebagai FValidTIFF := PopWord = 42 tidak salah soal TIFF klasik, tetapi ia menciutkan dua penolakan yang sangat berbeda menjadi satu boolean sunyi, sehingga BigTIFF tak bisa dibedakan dari JPEG terpotong yang di-rename seseorang. PDFlibPas kini memisahkan kasus-kasus itu dan mencatat masing-masing di TPDFTIFF.LastError: header lebih pendek dari empat byte, penanda byte-order tidak valid, magic 43, dan nilai magic lain semuanya menghasilkan teks yang berbeda. Pustaka ini tetap tidak mendekode BigTIFF, dan mengatakannya dengan gamblang justru intinya. Pemanggil mendapatkan perbedaan antara "ini bukan TIFF" dan "ini TIFF yang tata letak offset 64-bit-nya tidak diimplementasikan dekoder bawaan", yang merupakan perbedaan antara tiket dukungan yang bisa dijawab dalam satu balasan dan tiket yang berubah menjadi seminggu penuh tebakan
var
Tiff: TPDFTIFF;
Page: Integer;
begin
Tiff := TPDFTIFF.Create;
try
Tiff.LoadFromFile('inbox\scan-0417.tif');
if not Tiff.ValidTIFF then
raise Exception.Create('TIFF rejected: ' + Tiff.LastError);
if Tiff.PageCount < 1 then
raise Exception.Create('TIFF carries no decodable page');
for Page := 1 to Tiff.PageCount do
Writeln(Format('page %d: %dx%d, %d spp',
[Page,
Tiff.PageInfo[Page].Width,
Tiff.PageInfo[Page].Height,
Tiff.PageInfo[Page].SamplesPerPixel]));
finally
Tiff.Free;
end;
end;
Tile adalah geometri yang berbeda, bukan array offset lain
PDFlibPas menolak TIFF tiled saat mem-parsing tag, sebelum data piksel mana pun disentuh. Jalan pintas yang mengundang bug ini mudah terlihat: tag 324 (TileOffsets) dan tag 325 (TileByteCounts) adalah array offset berkas dan jumlah byte, secara struktural identik dengan array strip, sehingga mengarahkan field strip yang sudah ada ke keduanya hanya butuh dua baris dan terkompilasi bersih. Itu juga salah. Tile membentuk grid dua dimensi dengan blok tepi yang di-pad, stride barisnya sendiri di dalam setiap tile, dan tanpa semantik RowsPerStrip sama sekali, sebagaimana diuraikan bagian tiled-image pada TIFF 6.0. Memberikan payload tile ke dekoder strip karenanya tidak gagal dengan keras. SimpleExtract dan CompDecode menyusuri data dengan stride yang salah dan menghasilkan gambar dengan dimensi yang benar dan piksel yang salah. Kode lama memperparahnya dengan menyimpan StripsAreTiles, ColumnsPerTile dan RowsPerTile di TTIFFPage: geometri tile yang dicatat oleh dekoder tanpa perakit tile sama sekali. Di 3.534.1, handler untuk tag 324 dan 325 memunculkan error tile dan langsung meninggalkan IFD, sehingga penolakan itu membawa kata "tiled" alih-alih muncul berminggu-minggu kemudian sebagai keluhan rendering
Satu clamp dimensi bukanlah anggaran memori
Membatasi lebar dan tinggi masing-masing ke 65.535 itu perlu namun jauh dari cukup, karena besaran yang menggerakkan alokasi adalah hasil perkalian. RowsPerStrip * Width * SamplesPerPixel bisa overflow dalam aritmetika 32-bit jauh sebelum salah satu sisi mencapai batasnya sendiri, dan bahkan tanpa overflow ia bisa menyebut alokasi yang tak seharusnya dicoba layanan mana pun. PDFlibPas menghitung byte baris dalam Int64 dan menegakkan tiga plafon sekaligus: 65.535 per dimensi, 32 komponen warna, dan 256 MiB byte terdekode
const
PDFLIB_TIFF_MAX_IMAGE_DIM = 65535;
PDFLIB_TIFF_MAX_COLOR_COMPONENTS = 32;
PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES = 256 * 1024 * 1024;
// di dalam TPDFTIFF.ValidatePageForDecode
BitsPerPixel := Int64(P.BitsPerSample) * P.SamplesPerPixel;
RowBytes := (Int64(P.Width) * BitsPerPixel + 7) div 8;
if (RowBytes < 1) or
(RowBytes > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES) or
(Int64(P.Height) > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES div RowBytes) then
Exit(False);
DecodedBytes := RowBytes * P.RowsPerStrip;
if (DecodedBytes < 1) or
(DecodedBytes > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES) or
(DecodedBytes > MaxInt) then
Exit(False);
Tiga detail di sana lebih penting daripada konstantanya sendiri. Uji tinggi ditulis sebagai pembagian, bukan perkalian, sehingga produk yang terlalu besar tidak pernah terbentuk sama sekali. RowsPerStrip di bawah 1 atau di atas tinggi gambar dinormalisasi ke tinggi terlebih dahulu, yang sudah tersirat dalam pembacaan single-strip pada TIFF 6.0 dan menghentikan tag jahat dari menggelembungkan buffer strip. Dan rutinitasnya bersifat bersama: ValidatePageForDecode berjalan di akhir parsing tag dan lagi di pintu masuk SimpleExtract maupun CompDecode, sehingga kode yang mencapai dekoder secara langsung tidak bisa memutar menghindari anggaran. Itu aturan yang sama yang diikuti PDFlibPas saat mem-parsing graf objek PDF yang tidak tepercaya, karena batasan yang ditegakkan di satu dari tiga pintu bukanlah batasan
Apa yang harus diperiksa pemanggil sebelum membaca PageInfo?
Periksa ValidTIFF lebih dulu, lalu PageCount, dan baru kemudian indeks PageInfo. Berkas yang ditolak bisa meninggalkan PageCount di nol, dan GetPageInfo menjawab indeks di luar jangkauan dengan record TTIFFPage yang belum diinisialisasi, sehingga jalur error yang membaca resolusi atau jumlah sampel di tengah jalan menuju pelaporannya malah membaca noise. Versi 3.534.1 memperbaiki kedua pemanggil di dalam pustaka: jalur impor gambar membaca XRes dan YRes hanya di dalam cabang valid, dan TPDFlib.GetImagePageCount mensyaratkan ValidTIFF alih-alih memercayai jumlah halaman non-nol dengan sendirinya. Di hilir, argumen Options milik AddImageFromFile adalah nomor halaman berbasis 1 untuk TIFF multi-halaman, sehingga GetImagePageCount harus bisa dipercaya sebelum loop dimulai, bukan setelahnya. Nol halaman kini adalah jawaban nyata yang berarti "tidak ada yang bisa didekode di sini", bukan kecelakaan dari return dini, yang paling berarti saat Anda menyusun dan menyisipkan batch pemindaian dupleks dan satu lembar yang terdekode salah secara sunyi akan mendarat di posisi yang keliru
var
Pdf: TPDFlib;
Pages, I, ImageID: Integer;
begin
Pdf := TPDFlib.Create;
try
Pages := Pdf.GetImagePageCount('inbox\scan-0417.tif');
if Pages < 1 then
Exit; // header buruk, BigTIFF, tata letak tiled, atau melebihi anggaran
Pdf.NewDocument;
for I := 1 to Pages do
begin
Pdf.NewPage;
ImageID := Pdf.AddImageFromFile('inbox\scan-0417.tif', I);
if ImageID > 0 then
begin
Pdf.SelectImage(ImageID);
Pdf.DrawImage(0, 0, 595, 842);
end;
end;
Pdf.SaveToFile('scan-0417.pdf');
finally
Pdf.Free;
end;
end;
Membangun dekoder sendiri atau menautkan libtiff?
PDFlibPas mempertahankan dekoder bawaan, dan faktor penentunya adalah jangkauan platform, bukan siapa penulisnya. Kira-kira 1.873 baris Object Pascal terkompilasi di mana pun kompilator pergi: Win32, Win64, macOS, iOS, Android, dan FPC di Linux. libtiff 4.7.1 berjumlah sekitar 30.000 baris C yang tersebar di 34 unit translasi tif_*.c, dan file objek jadi yang ada hari ini hanya mencakup Windows. Mengadopsinya berarti menukar cakupan TIFF yang lengkap dengan daftar platform yang didukung yang menyusut ke mesin apa pun yang bisa menjalankan toolchain C, plus satu lintasan linker yang belum pernah dilalui siapa pun
Apa yang hilang karena itu layak dinyatakan tanpa dibungkus-bungkus. Dekoder bawaan menangani apa yang benar-benar dihasilkan pekerjaan dokumen hasil pindai: CCITT Group 3 satu dimensi dan dua dimensi, Group 4, LZW, Deflate, PackBits, dan JPEG-in-TIFF, lintas fotometrik WhiteIsZero, BlackIsZero, RGB, palet, dan CMYK dengan Predictor 1 dan 2. Payload-payload itu sejalan dengan filter PDF dalam ISO 32000-1 §7.4.4 dan §7.4.6, itulah sebabnya front-end TIFF membawa bobot besar dalam pipeline pemindaian. Yang tidak ditangani adalah BigTIFF, tile, Predictor 3 floating-point, PixarLog dan SGILog, kompresi JPEG gaya lama 6, serta piramida sub-IFD. Sejak 3.534.1, setiap hal itu adalah penolakan bernama alih-alih gambar yang salah, dan pustaka menyimpan daftar pemicu tertulis untuk membuka kembali keputusan libtiff:
- seorang pelanggan melaporkan berkas BigTIFF dan butuh dukungan native alih-alih langkah konversi
- seorang pelanggan melaporkan TIFF tiled dari sumber medis, GIS, atau industri dan butuh didekode langsung di tempat
- seorang pelanggan melaporkan TIFF Predictor 3 floating-point
- sebuah kerentanan yang dipublikasikan menghantam jalur dekode CCITT atau LZW bawaan
- argumen lintas platform berhenti berlaku, entah karena dukungan macOS, iOS, dan Android dihentikan, atau karena integrasi libtiff yang dapat dipakai ulang sudah mencakup macOS dan Linux
Migrasinya sendiri terukur, bukan hipotesis: conditional USE_LIBTIFF akan menjaga permukaan publik TPDFTIFF tetap utuh, mengarahkan LoadFromStream melalui TIFFClientOpen dengan callback stream, dan membiarkan parser Pascal sebagai fallback non-Windows. Sampai salah satu pemicu itu benar-benar terjadi, memelihara dua dekoder dan matriks uji yang berlipat tidak membeli apa pun yang bisa dirasakan pelanggan. Menunda biaya dengan rute pelarian yang sudah tertulis adalah hal yang berbeda dari mengabaikannya
Di mana posisi ini dalam pipeline dokumen hasil pindai
Perlakukan TPDFTIFF sebagai gerbang, bukan konverter. Muat berkasnya, baca ValidTIFF, dan catat LastError apa adanya setiap kali bernilai false, karena string itu kini adalah rute terpendek dari laporan lapangan ke diagnosis. Berkas yang gagal di gerbang tetap dapat diselamatkan dengan mengonversinya di hulu, yang merupakan jawaban praktis untuk sumber BigTIFF dan tiled hari ini. Untuk input di luar TIFF sepenuhnya, PDFlibPas mengambil rute terpisah melalui jalur input gambar AVIF, HEIF dan JPEG XL-nya, sehingga pertanyaan dekoder mana yang memiliki format mana tetap eksplisit, bukan muncul sendiri
Semua ini berada di balik API gambar biasa, sehingga pipeline dokumen mendapatkan batas yang lebih ketat tanpa mengubah satu baris kode pemanggil pun, di luar memeriksa jumlah halaman yang memang seharusnya sudah diperiksa. Jika Anda sedang menimbang jalur TIFF-ke-PDF native untuk Delphi atau C++Builder, komponen lengkap beserta penanganan gambarnya terdokumentasi di halaman PDF Library for Delphi