This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 4da5559c3af058d88b04c8e84f07380a184f48f2 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Aug 18 18:39:16 2022 +0200 Fix an EOF exception when reading a GeoTIFF image using LZW compression but without EOI (End Of Information) code. --- .../sis/internal/storage/inflater/CCITTRLE.java | 8 ++++-- .../storage/inflater/CompressionChannel.java | 19 +++++++++---- .../sis/internal/storage/inflater/Inflater.java | 26 +++++++++--------- .../apache/sis/internal/storage/inflater/LZW.java | 32 ++++++++++++++++++---- .../sis/internal/storage/inflater/PackBits.java | 10 ++++--- .../apache/sis/internal/storage/inflater/ZIP.java | 8 ++++-- .../sis/storage/geotiff/CompressedSubset.java | 2 +- .../org/apache/sis/storage/geotiff/DataCube.java | 16 +++++------ .../org/apache/sis/storage/geotiff/DataSubset.java | 4 +-- .../internal/storage/inflater/CCITTRLETest.java | 2 +- 10 files changed, 81 insertions(+), 46 deletions(-) diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java index cf44863623..b89e2c2dd4 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CCITTRLE.java @@ -18,6 +18,7 @@ package org.apache.sis.internal.storage.inflater; import java.io.IOException; import java.nio.ByteBuffer; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.internal.storage.io.ChannelDataInput; @@ -41,7 +42,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput; * which is not equivalent to "0011" neither. Consequently we can not parse directly the bits as integer values. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -120,10 +121,11 @@ final class CCITTRLE extends CompressionChannel { * before a reading process can start. * * @param input the source of data to decompress. + * @param listeners object where to report warnings. * @param sourceWidth number of pixels in a row of the source image. */ - CCITTRLE(final ChannelDataInput input, final int sourceWidth) { - super(input); + CCITTRLE(final ChannelDataInput input, final StoreListeners listeners, final int sourceWidth) { + super(input, listeners); bitsPerRow = sourceWidth; } diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java index ab41f72001..4fe15fc575 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java @@ -23,6 +23,7 @@ import org.apache.sis.math.MathFunctions; import org.apache.sis.internal.util.Numerics; import org.apache.sis.internal.geotiff.Resources; import org.apache.sis.internal.storage.io.ChannelDataInput; +import org.apache.sis.storage.event.StoreListeners; /** @@ -53,15 +54,22 @@ abstract class CompressionChannel extends PixelChannel { */ private long endPosition; + /** + * Objects where to report warnings. + */ + protected final StoreListeners listeners; + /** * Creates a new channel which will decompress data from the given input. * The {@link #setInputRegion(long, long)} method must be invoked after construction * before a reading process can start. * - * @param input the source of data to decompress. + * @param input the source of data to decompress. + * @param listeners object where to report warnings. */ - protected CompressionChannel(final ChannelDataInput input) { - this.input = input; + protected CompressionChannel(final ChannelDataInput input, final StoreListeners listeners) { + this.input = input; + this.listeners = listeners; } /** @@ -140,10 +148,9 @@ abstract class CompressionChannel extends PixelChannel { } /** - * Returns the resources for error messages. Current implementation does not know the locale. - * But if this information become known in a future version, this is the code to update. + * Returns the resources for error messages. */ final Resources resources() { - return Resources.forLocale(null); + return Resources.forLocale(listeners.getLocale()); } } diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java index cb6137895b..14a101a32b 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/Inflater.java @@ -28,8 +28,8 @@ import org.apache.sis.internal.geotiff.Predictor; import org.apache.sis.internal.geotiff.Resources; import org.apache.sis.internal.storage.io.ChannelDataInput; import org.apache.sis.storage.UnsupportedEncodingException; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.util.ArgumentChecks; -import org.apache.sis.util.Localized; import static org.apache.sis.internal.util.Numerics.ceilDiv; @@ -46,7 +46,7 @@ import static org.apache.sis.internal.util.Numerics.ceilDiv; * and band subset.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -167,7 +167,7 @@ public abstract class Inflater implements Closeable { * (e.g. 1 bit) if {@code pixelsPerElement} is greater than 1. If that case, the {@link #elementsPerChunk} * and {@link #skipAfterChunks} values will be divided by {@code pixelsPerElement}. * - * @param caller object calling this method (used in case an error message most be produced). + * @param listeners object where to report warnings. * @param input the source of data to decompress. * @param compression the compression method. * @param predictor the mathematical operator to apply after decompression. @@ -188,7 +188,7 @@ public abstract class Inflater implements Closeable { * in a way that make them usable with other tiled formats than TIFF. */ @SuppressWarnings("fallthrough") - public static Inflater create(final Localized caller, + public static Inflater create(final StoreListeners listeners, final ChannelDataInput input, final Compression compression, final Predictor predictor, @@ -204,18 +204,18 @@ public abstract class Inflater implements Closeable { ArgumentChecks.ensureNonNull("input", input); final CompressionChannel inflater; switch (compression) { - case LZW: inflater = new LZW (input); break; - case DEFLATE: inflater = new ZIP (input); break; - case PACKBITS: inflater = new PackBits(input); break; - case CCITTRLE: inflater = new CCITTRLE(input, sourceWidth); break; + case LZW: inflater = new LZW (input, listeners); break; + case DEFLATE: inflater = new ZIP (input, listeners); break; + case PACKBITS: inflater = new PackBits(input, listeners); break; + case CCITTRLE: inflater = new CCITTRLE(input, listeners, sourceWidth); break; case NONE: { if (predictor == Predictor.NONE) { return CopyFromBytes.create(input, dataType, chunksPerRow, samplesPerChunk, skipAfterChunks, pixelsPerElement); } - throw unsupportedEncoding(caller, Resources.Keys.UnsupportedPredictor_1, predictor); + throw unsupportedEncoding(listeners, Resources.Keys.UnsupportedPredictor_1, predictor); } default: { - throw unsupportedEncoding(caller, Resources.Keys.UnsupportedCompressionMethod_1, compression); + throw unsupportedEncoding(listeners, Resources.Keys.UnsupportedCompressionMethod_1, compression); } } final PixelChannel channel; @@ -236,7 +236,7 @@ public abstract class Inflater implements Closeable { // Fallthrough. } default: { - throw unsupportedEncoding(caller, Resources.Keys.UnsupportedPredictor_1, predictor); + throw unsupportedEncoding(listeners, Resources.Keys.UnsupportedPredictor_1, predictor); } } final int scanlineStride = Math.multiplyExact(sourceWidth, sourcePixelStride * dataType.bytes()); @@ -247,8 +247,8 @@ public abstract class Inflater implements Closeable { /** * Returns the exception to throw for an unsupported compression or predictor. */ - private static UnsupportedEncodingException unsupportedEncoding(final Localized caller, final short key, final Enum<?> value) { - return new UnsupportedEncodingException(Resources.forLocale(caller.getLocale()).getString(key, value)); + private static UnsupportedEncodingException unsupportedEncoding(StoreListeners listeners, short key, Enum<?> value) { + return new UnsupportedEncodingException(Resources.forLocale(listeners.getLocale()).getString(key, value)); } /** diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java index 30de03fb9c..89d35d20dd 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/LZW.java @@ -18,9 +18,11 @@ package org.apache.sis.internal.storage.inflater; import java.util.Arrays; import java.io.IOException; +import java.io.EOFException; import java.nio.ByteBuffer; import org.apache.sis.internal.geotiff.Resources; import org.apache.sis.internal.storage.io.ChannelDataInput; +import org.apache.sis.storage.event.StoreListeners; /** @@ -234,10 +236,11 @@ final class LZW extends CompressionChannel { * The {@link #setInputRegion(long, long)} method must be invoked after construction * before a reading process can start. * - * @param input the source of data to decompress. + * @param input the source of data to decompress. + * @param listeners object where to report warnings. */ - public LZW(final ChannelDataInput input) { - super(input); + public LZW(final ChannelDataInput input, final StoreListeners listeners) { + super(input, listeners); indexOfFreeEntry = FIRST_ADAPTATIVE_CODE; entriesForCodes = new int[(1 << MAX_CODE_SIZE) - OFFSET_TO_MAXIMUM]; stringsFromCode = new byte[3 << (MAX_CODE_SIZE + STRING_ALIGNMENT)]; // Dynamically expanded if needed. @@ -275,6 +278,25 @@ final class LZW extends CompressionChannel { codeSize = MIN_CODE_SIZE; } + /** + * Reads {@link #codeSize} bits from the stream. + * + * @return the value of the next bits from the stream. + * @throws IOException if an error occurred while reading. + */ + public final int readNextCode() throws IOException { + try { + return (int) input.readBits(codeSize); + } catch (EOFException e) { + if (finished()) { + listeners.warning(null, e); + return EOI_CODE; + } else { + throw e; + } + } + } + /** * Decompresses some bytes from the {@linkplain #input input} into the given destination buffer. * @@ -310,7 +332,7 @@ final class LZW extends CompressionChannel { int stringLength = length(entryForCode); int maximumIndex = (1 << codeSize) - OFFSET_TO_MAXIMUM; int code; - while ((code = (int) input.readBits(codeSize)) != EOI_CODE) { // GetNextCode() + while ((code = readNextCode()) != EOI_CODE) { // GetNextCode() if (code == CLEAR_CODE) { clearTable(); // InitializeTable() maximumIndex = (1 << MIN_CODE_SIZE) - OFFSET_TO_MAXIMUM; @@ -319,7 +341,7 @@ final class LZW extends CompressionChannel { * The first valid code after `CLEAR_CODE` shall be a byte. If we reached the end * of strip, the EOI code should be mandatory but appears to be sometime missing. */ - do code = finished() ? EOI_CODE : (int) input.readBits(MIN_CODE_SIZE); // GetNextCode() + do code = readNextCode(); // GetNextCode() while (code == CLEAR_CODE); if ((code & ~0xFF) != 0) { if (code == EOI_CODE) break; diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java index 650633f930..c4b6fbc431 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PackBits.java @@ -18,6 +18,7 @@ package org.apache.sis.internal.storage.inflater; import java.io.IOException; import java.nio.ByteBuffer; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.internal.storage.io.ChannelDataInput; @@ -26,7 +27,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput; * This compression is described in section 9 of TIFF 6 specification. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -53,10 +54,11 @@ final class PackBits extends CompressionChannel { * The {@link #setInputRegion(long, long)} method must be invoked after construction * before a reading process can start. * - * @param input the source of data to decompress. + * @param input the source of data to decompress. + * @param listeners object where to report warnings. */ - public PackBits(final ChannelDataInput input) { - super(input); + public PackBits(final ChannelDataInput input, final StoreListeners listeners) { + super(input, listeners); } /** diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java index a4f5c1fb35..4e4ccc4ec5 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/ZIP.java @@ -21,6 +21,7 @@ import java.nio.ByteBuffer; import java.nio.BufferOverflowException; import java.util.zip.Inflater; import java.util.zip.DataFormatException; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.internal.storage.io.ChannelDataInput; @@ -29,7 +30,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput; * * @author Rémi Marechal (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -46,12 +47,13 @@ final class ZIP extends CompressionChannel { * before a reading process can start. * * @param input the source of data to decompress. + * @param listeners object where to report warnings. * @param start stream position where to start reading. * @param byteCount number of bytes to read from the input. * @throws IOException if the stream can not be seek to the given start position. */ - public ZIP(final ChannelDataInput input) { - super(input); + public ZIP(final ChannelDataInput input, final StoreListeners listeners) { + super(input, listeners); inflater = new Inflater(); } diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java index 3151824b39..2566aac94e 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java @@ -216,7 +216,7 @@ final class CompressedSubset extends DataSubset { final int pixelsPerElement = getPixelsPerElement(); // Always ≥ 1 and usually = 1. assert (head % pixelsPerElement) == 0 : head; if (inflater == null) { - inflater = Inflater.create(this, input(), source.getCompression(), source.getPredictor(), + inflater = Inflater.create(source.listeners(), input(), source.getCompression(), source.getPredictor(), sourcePixelStride, getTileSize(X_DIMENSION), chunksPerRow, samplesPerChunk, skipAfterChunks, pixelsPerElement, dataType); } diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java index 664ca72231..99b7a22a70 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java @@ -16,7 +16,6 @@ */ package org.apache.sis.storage.geotiff; -import java.util.Locale; import java.util.Optional; import java.nio.file.Path; import java.awt.image.DataBuffer; @@ -26,6 +25,7 @@ import org.opengis.util.GenericName; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.internal.geotiff.Resources; @@ -43,8 +43,11 @@ import org.apache.sis.math.Vector; * But it can also be a stack of images organized in a <var>n</var>-dimensional data cube, * or a pyramid of images with their overviews used when low resolution images is requested. * + * <p><b>Warning:</b> do not implement {@link org.apache.sis.util.Localized}, + * as it may cause an infinite loop in {@code listeners.getLocale()} call.</p> + * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -82,13 +85,10 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste } /** - * Returns the locale for warnings and error messages. - * - * <p><b>Warning:</b> do not implement {@link org.apache.sis.util.Localized}, - * as it may cause an infinite loop in {@code listeners.getLocale()} call.</p> + * Access to the protected {@link #listeners} field. */ - final Locale getLocale() { - return listeners.getLocale(); + final StoreListeners listeners() { + return listeners; } /** diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java index e003987e00..03af798ecd 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java @@ -66,7 +66,7 @@ import static java.lang.Math.toIntExact; * the same tile indices than {@link DataCube} in order to avoid integer overflow. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.1 * @module */ @@ -181,7 +181,7 @@ class DataSubset extends TiledGridCoverage implements Localized { */ @Override public final Locale getLocale() { - return source.getLocale(); + return source.listeners().getLocale(); } /** diff --git a/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java b/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java index ad0903138d..18339f1546 100644 --- a/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java +++ b/storage/sis-geotiff/src/test/java/org/apache/sis/internal/storage/inflater/CCITTRLETest.java @@ -500,7 +500,7 @@ public final strictfp class CCITTRLETest extends TestCase { private void verifyReading() throws IOException { final CCITTRLE decoder = new CCITTRLE( new ChannelDataInput("sequenceOfAllWords", null, sequenceOfAllWords, true), - sequenceOfAllWords.limit()); + null, sequenceOfAllWords.limit()); decoder.setInputRegion(0, sequenceOfAllWords.limit()); int runLength = 0; do {