From db5635e844c6354dfa3aa4648bf46e8500204ba4 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 16 Jun 2020 21:54:16 +0200 Subject: [PATCH] #535: Detect incorrect compression in TIFF CCITT stream. --- .../plugins/tiff/CCITTFaxDecoderStream.java | 49 ++++++++++++++++-- .../imageio/plugins/tiff/TIFFImageReader.java | 12 ++++- .../tiff/CCITTFaxDecoderStreamTest.java | 29 +++++++++++ .../plugins/tiff/TIFFImageReaderTest.java | 19 +++++++ .../tiff/incorrect-compression-rle-as-g3.tif | Bin 0 -> 853 bytes 5 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/incorrect-compression-rle-as-g3.tif diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java index e4d98961..878142d1 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java @@ -91,11 +91,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream { super(Validate.notNull(stream, "stream")); this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0"); - this.type = Validate.isTrue( - type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE || - type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6, - type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s" - ); + this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE || + type == TIFFExtension.COMPRESSION_CCITT_T4 || + type == TIFFExtension.COMPRESSION_CCITT_T6, + type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"); this.fillOrder = Validate.isTrue( fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT, fillOrder, "Expected fill order 1 or 2: %s" @@ -150,6 +149,46 @@ final class CCITTFaxDecoderStream extends FilterInputStream { this(stream, columns, type, fillOrder, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE); } + static int findCompressionType(final int type, final InputStream in) throws IOException { + // Discover possible incorrect type, revert to RLE + if (type == TIFFExtension.COMPRESSION_CCITT_T4 && in.markSupported()) { + byte[] streamData = new byte[20]; + + try { + in.mark(streamData.length); + + int offset = 0; + while (offset < streamData.length) { + int read = in.read(streamData, offset, streamData.length - offset); + if (read <= 0) { + break; + } + + offset += read; + } + } + finally { + in.reset(); + } + + if (streamData[0] != 0 || (streamData[1] >> 4 != 1 && streamData[1] != 1)) { + // Leading EOL (0b000000000001) not found, search further and try RLE if not found + short b = (short) (((streamData[0] << 8) + streamData[1]) >> 4); + for (int i = 12; i < 160; i++) { + b = (short) ((b << 1) + ((streamData[(i / 8)] >> (7 - (i % 8))) & 0x01)); + + if ((b & 0xFFF) == 1) { + return TIFFExtension.COMPRESSION_CCITT_T4; + } + } + + return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE; + } + } + + return type; + } + private void fetch() throws IOException { if (decodedPos >= decodedLength) { decodedLength = 0; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index bacdcb8d..319fd30e 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -2308,12 +2308,22 @@ public final class TIFFImageReader extends ImageReaderBase { case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: case TIFFExtension.COMPRESSION_CCITT_T4: case TIFFExtension.COMPRESSION_CCITT_T6: - return new CCITTFaxDecoderStream(stream, width, compression, fillOrder, getCCITTOptions(compression)); + return new CCITTFaxDecoderStream(stream, width, findCCITTType(compression, stream), fillOrder, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE); default: throw new IllegalArgumentException("Unsupported TIFF compression: " + compression); } } + private int findCCITTType(final int encodedCompression, final InputStream stream) throws IOException { + int compressionType = CCITTFaxDecoderStream.findCompressionType(encodedCompression, stream); + + if (compressionType != encodedCompression) { + processWarningOccurred(String.format("Detected compression type %d, does not match encoded compression type: %d", compressionType, encodedCompression)); + } + + return compressionType; + } + private InputStream createFillOrderStream(final int fillOrder, final InputStream stream) { switch (fillOrder) { case TIFFBaseline.FILL_LEFT_TO_RIGHT: diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java index 6a305032..21946a1d 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -59,6 +59,14 @@ public class CCITTFaxDecoderStreamTest { static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 }; + // group3_1d_premature_eol.tif + // 0011 0101 | 0000 0010 1011 | 0110 0111 | 0010 1001 | 0100 + // 0W | 59B | 640W | 40W + static final byte[] DATA_G3_1D_PREMATURE_EOL = { + 0x35, 0x02, (byte) 0xB6, 0x72, (byte) 0x94, (byte) 0xE8, 0x74, 0x38, 0x1C, (byte) 0x81, 0x64, (byte) 0xD4, + 0x0A, (byte) 0xD9, (byte) 0xD2, 0x27, 0x50, (byte) 0x90, (byte) 0xA6, (byte) 0x87, 0x43, (byte) 0xE3 + }; + // group3_2d.tif: EOL|k=1|3W|1B|2W|EOL|k=0|V|V|V|EOL|k=1|3W|1B|2W|EOL|k=0|V-1|V|V|6*F static final byte[] DATA_G3_2D = { 0x00, 0x1C, 0x27, 0x00, 0x17, 0x00, 0x1C, 0x27, 0x00, 0x12, (byte) 0xC0 }; @@ -170,6 +178,27 @@ public class CCITTFaxDecoderStreamTest { assertArrayEquals(imageData, bytes); } + @Test + public void testFidCompressionType() throws IOException { + // RLE + assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, new ByteArrayInputStream(DATA_RLE_UNALIGNED))); + + // Group 3/CCITT_T4 + assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D))); + assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D_FILL))); + assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D))); + assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_FILL))); + assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_lsb2msb))); + + // Group 4/CCITT_T6 + assertEquals(TIFFExtension.COMPRESSION_CCITT_T6, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T6, new ByteArrayInputStream(DATA_G4))); + assertEquals(TIFFExtension.COMPRESSION_CCITT_T6, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T6, new ByteArrayInputStream(DATA_G4_ALIGNED))); + + // From sample file encoded with RLE, but with CCITT_T4 compression tag + assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D_PREMATURE_EOL))); + assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_RLE_UNALIGNED))); + } + @Test public void testDecodeType3_2D() throws IOException { InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6, diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index b62f838d..2eab7b79 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -638,6 +638,25 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest^rY?3nbHhqA5DgY%LCl@`FBfKF3I?9VHmBvZ3sUmMG4&)u zgbb#%*oAJ>7s1GS8LJgqB%E{V1cQwkY?0v0NQi#7uGov?_u=9DJonu9cNPm&2|?n1 zz4*=WY>G1A^zEo}H&Wg@<70?ql3vpwroD$_&-zg2Rx)5}3hl#4i*snrS(`|Fu(c6h zF3qLJHWVE1eH_If4CC0`rE4Xe>dQZl9F1>E#A1ie9wkc7j}Xr@Y%^rY;#N zBWwO9A2;F(?_N~ZH52f8dMV&~Zd^i{y3HhAtzIWXU)>}cV2DL~5X4DCZF(6QJ3Ojx zGVdd~eqvEh0YB|_AY~JHiCfMz65!3nC_}K3{O_-)oM)tZ#1(vxV(N)UF}{+T<x3UlfSR^9u9^a?iFy zdhnG0hP-zn{*QqmXbn7~f2VoScK_b>K`W&thRM;9_NMxJZM#KVT~(tc2b^{if(oJE zkR)RfXM7ok7XXU{T?<$&FbEb&1O}5LSYSRtnKe@&upH>cS-MJKKJ&z17FPkrAX%mZ oBsPjP|Jf|Uv%IGSmV)`y0`sN{f%*Ptv-BDWD3A`Ur5T*iAKj4%(EtDd literal 0 HcmV?d00001