mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
Fixes an issue where the CCITTFaxDecoderStream could cause endless reading (and potential OOME)
This commit is contained in:
parent
7fc47a338c
commit
f4a5f57d52
@ -50,6 +50,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
||||
|
||||
private final int columns;
|
||||
private int rowsLeft;
|
||||
private final byte[] decodedRow;
|
||||
|
||||
private final boolean optionG32D;
|
||||
@ -70,6 +71,19 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
|
||||
private int lastChangingElement = 0;
|
||||
|
||||
/**
|
||||
* Creates a CCITTFaxDecoderStream.
|
||||
*
|
||||
* @param stream the compressed CCITT stream.
|
||||
* @param columns the number of columns in the stream.
|
||||
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
||||
* @param options CCITT T.4 or T.6 options.
|
||||
*/
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final long options) {
|
||||
this(stream, columns, -1, type, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CCITTFaxDecoderStream.
|
||||
* This constructor may be used for CCITT streams embedded in PDF files,
|
||||
@ -82,15 +96,20 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
* @param options CCITT T.4 or T.6 options.
|
||||
* @param byteAligned enable byte alignment used in PDF files (EncodedByteAlign).
|
||||
*/
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type,
|
||||
final long options, final boolean byteAligned) {
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final long options, final boolean byteAligned) {
|
||||
this(stream, columns, -1, type, options, byteAligned);
|
||||
}
|
||||
|
||||
CCITTFaxDecoderStream(final InputStream stream, final int columns, final int rows, final int type, final long options, final boolean byteAligned) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||
// -1 means as many rows the stream contains, otherwise pad up to 'rows' before EOF for compatibility with legacy CCITT streams
|
||||
this.rowsLeft = Validate.isTrue(rows == -1 || rows > 0, rows, "height 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");
|
||||
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");
|
||||
|
||||
// We know this is only used for b/w (1 bit)
|
||||
decodedRow = new byte[(columns + 7) / 8];
|
||||
@ -122,21 +141,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CCITTFaxDecoderStream.
|
||||
*
|
||||
* @param stream the compressed CCITT stream.
|
||||
* @param columns the number of columns in the stream.
|
||||
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
||||
* @param options CCITT T.4 or T.6 options.
|
||||
*/
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type,
|
||||
final long options) {
|
||||
this(stream, columns, type, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||
}
|
||||
|
||||
static int findCompressionType(final int encodedType, final InputStream stream) throws IOException {
|
||||
@ -209,9 +214,18 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// ...otherwise, just let client code try to read past the
|
||||
// end of stream
|
||||
decodedLength = -1;
|
||||
if (rowsLeft > 0) {
|
||||
// For
|
||||
rowsLeft--;
|
||||
|
||||
Arrays.fill(decodedRow, (byte) 0);
|
||||
decodedLength = decodedRow.length;
|
||||
}
|
||||
else {
|
||||
// ...otherwise, just let client code try to read past the
|
||||
// end of stream
|
||||
decodedLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
decodedPos = 0;
|
||||
@ -404,7 +418,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
int byteIndex = index / 8;
|
||||
|
||||
while (index % 8 != 0 && (nextChange - index) > 0) {
|
||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
decodedRow[byteIndex] |= (byte) (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
index++;
|
||||
}
|
||||
|
||||
@ -424,7 +438,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
decodedRow[byteIndex] = 0;
|
||||
}
|
||||
|
||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
decodedRow[byteIndex] |= (byte) (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
index++;
|
||||
}
|
||||
|
||||
@ -435,6 +449,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
||||
}
|
||||
|
||||
rowsLeft--;
|
||||
decodedLength = (index + 7) / 8;
|
||||
}
|
||||
|
||||
@ -494,14 +509,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return 0x0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return 0x0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,16 +526,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
Arrays.fill(b, off, off + len, (byte) 0x0);
|
||||
return len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
Arrays.fill(b, off, off + len, (byte) 0x0);
|
||||
return len;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1134,7 +1134,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
int compressedStripTileWidth = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR && b > 0 && yCbCrSubsampling != null
|
||||
? ((stripTileWidth + yCbCrSubsampling[0] - 1) / yCbCrSubsampling[0])
|
||||
: stripTileWidth;
|
||||
adapter = createDecompressorStream(compression, compressedStripTileWidth, samplesInTile, adapter);
|
||||
adapter = createDecompressorStream(compression, compressedStripTileWidth, stripTileHeight, samplesInTile, adapter);
|
||||
adapter = createUnpredictorStream(predictor, compressedStripTileWidth, samplesInTile, bitsPerSample, adapter, imageInput.getByteOrder());
|
||||
adapter = createYCbCrUpsamplerStream(interpretation, planarConfiguration, b, rowRaster.getTransferType(), yCbCrSubsampling, yCbCrPos, colsInTile, adapter, imageInput.getByteOrder());
|
||||
|
||||
@ -2506,7 +2506,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
return (short) Math.max(0, Math.min(0xffff, val));
|
||||
}
|
||||
|
||||
private InputStream createDecompressorStream(final int compression, final int width, final int bands, InputStream stream) throws IOException {
|
||||
private InputStream createDecompressorStream(final int compression, final int width, final int height, final int bands, InputStream stream) throws IOException {
|
||||
switch (compression) {
|
||||
case TIFFBaseline.COMPRESSION_NONE:
|
||||
return stream;
|
||||
@ -2527,7 +2527,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
if (overrideCCITTCompression == -1) {
|
||||
overrideCCITTCompression = findCCITTType(compression, stream);
|
||||
}
|
||||
return new CCITTFaxDecoderStream(stream, width, overrideCCITTCompression, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||
return new CCITTFaxDecoderStream(stream, width, height, overrideCCITTCompression, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||
}
|
||||
|
@ -157,8 +157,13 @@ public class CCITTFaxDecoderStreamTest {
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
|
||||
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||
dataInputStream.readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
|
||||
assertEquals(-1, dataInputStream.read());
|
||||
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -168,8 +173,13 @@ public class CCITTFaxDecoderStreamTest {
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
|
||||
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||
dataInputStream.readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
|
||||
assertEquals(-1, dataInputStream.read());
|
||||
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -179,8 +189,13 @@ public class CCITTFaxDecoderStreamTest {
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
|
||||
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||
dataInputStream.readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
|
||||
assertEquals(-1, dataInputStream.read());
|
||||
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -265,7 +280,7 @@ public class CCITTFaxDecoderStreamTest {
|
||||
new DataInputStream(inputStream).readFully(data);
|
||||
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
|
||||
6, TIFFExtension.COMPRESSION_CCITT_T6, 0L);
|
||||
6, 6, TIFFExtension.COMPRESSION_CCITT_T6, 0L, false);
|
||||
|
||||
byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
@ -274,8 +289,8 @@ public class CCITTFaxDecoderStreamTest {
|
||||
byte[] imageData = Arrays.copyOf(((DataBufferByte) image.getData().getDataBuffer()).getData(), 6);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
|
||||
// Ideally, we should have no more data now, but the stream don't know that...
|
||||
// assertEquals("Should contain no more data", -1, stream.read());
|
||||
assertEquals("Should contain no more data", -1, stream.read());
|
||||
assertEquals("Should contain no more data", -1, stream.read(new byte[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user