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 {

Reply via email to