PDFlibPas dekóduje TIFF ručne písaným analyzátorom v Object Pascale, nie väzbou na libtiff, a verzia 3.534.1 sprísnila presne tie miesta, kde tento analyzátor vstup odmieta. Magické číslo 43 pre BigTIFF sa teraz odmieta menovite, TileOffsets a TileByteCounts sa zamietajú už pri analýze tagov a každý buffer sa dimenzuje aritmetikou Int64 pod stropom 256 MiB pre dekódovanie
Chyba, ktorú to zatvára, sa nikdy neprejaví v laboratóriu. Prejaví sa ako skenovacia brána, ktorá tri roky beží potichu, kým zákazník nepustí cez ňu geografický archív alebo celoslajdové lekárske zobrazovanie. Súbor má legitímnu TIFF hlavičku. Analyzuje sa. Výsledkom je stránka pruhovaného šumu alebo viacgigabajtová alokácia, ktorá zrazí službu, a nič na ceste neoznámilo vstup ako neplatný. Toto je tvar zlyhania, proti ktorému sa oplatí inžinierstvo: nie pád, ale istota v podaní nesprávnej odpovede
Prečo II alebo MM nedokazuje, že máte klasické TIFF?
Pretože značku poradia bajtov zdieľajú oba dialekty. Klasické TIFF aj BigTIFF otvárajú II alebo MM, a pole, ktoré ich skutočne odlišuje, je 16-bitová magická hodnota hneď za ním: 42 pre klasické TIFF podľa špecifikácie TIFF 6.0, 43 pre BigTIFF s jeho 64-bitovými ofsetmi. Loader napísaný ako FValidTIFF := PopWord = 42 sa pri klasickom TIFF nemýli, ale zlučuje dve veľmi odlišné zamietnutia do jedného tichého booleanu, takže BigTIFF sa stáva nerozoznateľným od skrátenej JPEG, ktorú niekto premenoval. PDFlibPas teraz prípady rozdeľuje a každý zaznamenáva v TPDFTIFF.LastError: hlavička kratšia ako štyri bajty, neplatná značka poradia bajtov, magické 43 a každá iná magická hodnota dávajú odlišné texty. Knižnica BigTIFF stále nedekóduje, a to povedať nahlas je presne pointa. Volajúci dostane rozdiel medzi „toto nie je TIFF" a „toto je TIFF, ktorého 64-bitové rozloženie ofsetov vstavaný dekodér neimplementuje", čo je rozdiel medzi tiketom podpory, na ktorý odpoviete jednou správou, a tiketom, ktorý sa zmení na týždeň hádania
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;
Dlaždice sú iná geometria, nie ďalšie pole ofsetov
PDFlibPas zamieta dlaždicové TIFF už pri analýze tagov, skôr než sa čokoľvek dotkne pixelových dát. Skratka, ktorá chybu pozýva, je ľahko viditeľná: tag 324 (TileOffsets) a tag 325 (TileByteCounts) sú polia ofsetov do súboru a počtov bajtov, štrukturálne identické s poliami pruhov, takže nasmerovať existujúce polia pruhov na ne stojí dva riadky a preloží sa to čisto. Je to aj zlé. Dlaždice tvoria dvojrozmernú mriežku s vyplnenými okrajovými blokmi, vlastným krokom riadku vo vnútri každej dlaždice a žiadnou sémantikou RowsPerStrip, ako rozvádza sekcia tiled-image v TIFF 6.0. Poslať dátové bloky dlaždíc do dekodéra pruhov preto nezlyhá nahlas. SimpleExtract a CompDecode prechádzajú dáta nesprávnym krokom a vysypú obrázok so správnymi rozmermi a nesprávnymi pixelmi. Starší kód to ešte znásobil tým, že vo TTIFFPage držal StripsAreTiles, ColumnsPerTile a RowsPerTile: geometriu dlaždíc zaznamenanú dekodérom bez akéhokoľvek zostavovača dlaždíc v pozadí. V 3.534.1 obsluhy tagov 324 a 325 vyvolajú chybu dlaždice a IFD okamžite opustia, takže zamietnutie nesie slovo „tiled" namiesto toho, aby sa vynorilo o týždne neskôr ako sťažnosť na vykresľovanie
Jeden clamp rozmeru nie je rozpočet pamäte
Obmedziť šírku a výšku každú na 65 535 je nutné a zďaleka nestačí, lebo veličina, ktorá riadi alokáciu, je súčin. RowsPerStrip * Width * SamplesPerPixel môže pretekať 32-bitovú aritmetiku skôr, než niektorá strana dosiahne vlastnú hranicu, a aj bez pretečenia môže pomenovať alokáciu, o ktorú by sa žiadna služba nemala pokúšať. PDFlibPas počíta bajty riadku v Int64 a vynucuje tri stropy spolu: 65 535 na rozmer, 32 farebných komponent a 256 MiB dekódovaných bajtov
const
PDFLIB_TIFF_MAX_IMAGE_DIM = 65535;
PDFLIB_TIFF_MAX_COLOR_COMPONENTS = 32;
PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES = 256 * 1024 * 1024;
// vnútri 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);
Tri detaily tam záležia viac než konštanty samé. Test výšky je napísaný ako delenie, nie násobenie, takže prehnaný súčin sa nikdy nevytvorí. RowsPerStrip pod 1 alebo nad výšku obrázku sa najprv normalizuje na výšku, čo je čítanie jediného pruhu, ktoré TIFF 6.0 už naznačuje, a bráni nepriateľskému tagu napučať buffer pruhu. A rutina je zdieľaná: ValidatePageForDecode beží na konci analýzy tagov a znova pri vstupe do SimpleExtract aj CompDecode, takže kód, ktorý sa k dekodéru dostane priamo, nemôže obísť rozpočet. To je to isté pravidlo, ktoré PDFlibPas dodržiava pri analýze nedôveryhodných grafov objektov PDF, lebo limit vynútený na jedných z troch dverí nie je limit
Čo musí volajúci skontrolovať pred čítaním PageInfo?
Najprv skontrolovať ValidTIFF, potom PageCount a až potom indexovať PageInfo. Zamietnutý súbor môže nechať PageCount na nule a GetPageInfo odpovedá na index mimo rozsahu neinicializovaným záznamom TTIFFPage, takže chybová cesta, ktorá cestou k hláseniu zlyhania číta rozlíšenie alebo počty vzoriek, nakoniec číta šum. Verzia 3.534.1 opravila oboch volajúcich vnútri knižnice: cesta importu obrázkov číta XRes a YRes len vo vnútri platnej vetvy a TPDFlib.GetImagePageCount vyžaduje ValidTIFF namiesto toho, aby slepo dôveroval nenulovému počtu strán. Ďalej v poradí, argument Options v AddImageFromFile je číslo strany od 1 pre viacstránkové TIFF, takže GetImagePageCount musí byť dôveryhodný skôr, než sa slučka rozbehne, nie po nej. Nula strán je teraz skutočná odpoveď znamenajúca „tu nič nie je dekódovateľné", nie nehoda skorého návratu, čo najviac záleží, keď zlučujete a prekladáte obojstranné skenovacie dávky a jedna ticho zle dekódovaná strana by dopadla na nesprávne miesto
var
Pdf: TPDFlib;
Pages, I, ImageID: Integer;
begin
Pdf := TPDFlib.Create;
try
Pages := Pdf.GetImagePageCount('inbox\scan-0417.tif');
if Pages < 1 then
Exit; // zlá hlavička, BigTIFF, dlaždicové rozloženie alebo nad limit
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;
Vlastný dekodér, alebo linkovať libtiff?
PDFlibPas drží vstavaný dekodér a rozhodujúcim faktorom je dosah platforiem, nie autorstvo. Zhruba 1 873 riadkov Object Pascalu sa preloží všade, kam sa dostane kompilátor: Win32, Win64, macOS, iOS, Android a FPC na Linuxe. libtiff 4.7.1 je okolo 30 000 riadkov C roztrúsených po 34 tif_*.c prekladových jednotkách a predložené objektové súbory, ktoré dnes existujú, pokrývajú len Windows. Prijatie by obetovalo kompletné pokrytie TIFF za zoznam podporovaných platforiem, ktorý sa scvrkne na stroje, kde beží C toolchain, plus prechod linkerom, ktorým nikto ešte neprešiel
Čo to stojí, stojí za povedanie bez obzerania. Vstavaný dekodér zvláda to, čo skenované dokumenty reálne produkujú: CCITT Group 3 jednorozmerné a dvojrozmerné, Group 4, LZW, Deflate, PackBits a JPEG-in-TIFF, cez WhiteIsZero, BlackIsZero, RGB, paletu a CMYK fotometriu s Predictor 1 a 2. Tieto dátové bloky sa kryjú s PDF filtrami v ISO 32000-1 §7.4.4 a §7.4.6, preto má TIFF front-end vo skenovacom pipeline takú váhu. Nezvláda BigTIFF, dlaždice, Predictor 3 s pohyblivou čiarkou, PixarLog a SGILog, starý JPEG komprimačný režim 6 a pyramídy sub-IFD. Od 3.534.1 je každá z týchto vecí pomenované zamietnutie namiesto zlého obrázka a knižnica vedie písaný zoznam spúšťačov na znovuotvorenie rozhodnutia o libtiff:
- zákazník nahlási súbor BigTIFF a potrebuje natívnu podporu namiesto konverzného kroku
- zákazník nahlási dlaždicové TIFF z lekárskych, GIS alebo priemyselných zdrojov a potrebuje ich dekódovať na mieste
- zákazník nahlási TIFF s Predictor 3 v pohyblivej čiarkou
- publikovaná zraniteľnosť dopadne na vstavané dekódovacie cesty CCITT alebo LZW
- argument cross-platform prestane platiť, buď preto, že podpora macOS, iOS a Android sa zruší, alebo preto, že opakovane použiteľná integrácia libtiff už pokrýva macOS a Linux
Migrácia sama je ohraničená, nie hypotetická: podmienený USE_LIBTIFF by ponechal verejný povrch TPDFTIFF netknutý, nasmeroval LoadFromStream cez TIFFClientOpen so streamovými callbackmi a nechal Pascalovský analyzátor ako zálohu pre systémy mimo Windows. Kým niektorý z tých spúšťačov skutočne nevystrelí, udržiavanie dvoch dekodérov a zdvojeného testovacieho matrica nekúpi nič, čo by zákazník cítil. Odkladať náklad s už zapísanou únikovou cestou je niečo iné než ho ignorovať
Čo to znamená pre skenovací pipeline
Zobrať TPDFTIFF ako bránu, nie konvertor. Načítať súbor, prečítať ValidTIFF a zapisovať LastError doslova vždy, keď je nepravdivý, lebo tento reťazec je teraz najkratšia cesta od terénneho hlásenia k diagnóze. Súbory, ktoré bránou neprejdú, zostávajú záchraniteľné konverziou upstream, čo je dnes praktická odpoveď pre BigTIFF a dlaždicové zdroje. Pre vstup mimo TIFF úplne berie PDFlibPas samostatnú cestu cez vstup obrázkov AVIF, HEIF a JPEG XL, takže otázka, ktorý dekodér vlastní ktorý formát, zostáva explicitná namiesto samovolnej
Všetko toto sedí za obyčajným image API, takže dokumentový pipeline získa pevnejšiu hranicu bez zmeny jediného riadku volajúceho kódu okrem kontroly počtu strán, ktorú mal kontrolovať aj tak. Ak vážite natívnu cestu TIFF-to-PDF pre Delphi alebo C++Builder, celý komponent a jeho prácu s obrázkami dokumentuje stránka PDF Library for Delphi