This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 05be1817f6 Do not allow a Landsat MLT file to refer a TIFF image 
outside the scene directory.
05be1817f6 is described below

commit 05be1817f64eb93d9a35f8cc7a05616c83bb4442
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Sep 2 22:40:21 2026 +0900

    Do not allow a Landsat MLT file to refer a TIFF image outside the scene 
directory.
---
 .../main/module-info.java                          |   2 +-
 .../main/org/apache/sis/storage/landsat/Band.java  |  36 ++++--
 .../apache/sis/storage/landsat/LandsatStore.java   |  14 ++-
 .../sis/storage/landsat/LandsatStoreProvider.java  |   2 +-
 .../apache/sis/storage/landsat/MetadataReader.java |   6 +-
 .../sis/storage/landsat/internal/Resources.java    | 135 +++++++++++++++++++++
 .../storage/landsat/internal/Resources.properties  |  22 ++++
 .../storage/landsat/internal/Resources_en.java}    |  25 ++--
 .../storage/landsat/internal/Resources_fr.java}    |  25 ++--
 .../landsat/internal/Resources_fr.properties       |  27 +++++
 .../storage/landsat/internal/package-info.java}    |  21 ++--
 .../apache/sis/storage/landsat/package-info.java   |   2 +-
 .../storage/landsat/LandsatStoreProviderTest.java  |  76 +++++++++++-
 .../sis/storage/landsat/MetadataReaderTest.java    |   7 +-
 .../org.apache.sis.storage/main/module-info.java   |   1 +
 .../main/org/apache/sis/io/stream/IOUtilities.java |  17 +++
 16 files changed, 349 insertions(+), 69 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
index 37a0ea58ef..2d4985925f 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
@@ -22,7 +22,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.7
  * @since   0.8
  */
 module org.apache.sis.storage.earthobservation {
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
index 60554ae5d5..4ca9f84e49 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
@@ -23,6 +23,7 @@ import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.identification.Identification;
 import org.opengis.metadata.content.CoverageContentType;
+import org.apache.sis.io.stream.IOUtilities;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
@@ -39,6 +40,7 @@ import org.apache.sis.metadata.iso.content.DefaultBand;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.collection.Containers;
+import org.apache.sis.storage.landsat.internal.Resources;
 
 
 /**
@@ -67,11 +69,11 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
     LocalName identifier;
 
     /**
-     * Filename of the file to read for band data.
-     * This is relative to {@link LandsatStore#directory}.
+     * Path to the file to read for getting the band data.
+     * This is resolved against {@link LandsatStore#directory}.
      * Should not be modified after the end of metadata parsing.
      */
-    String filename;
+    private Path storage;
 
     /**
      * Metadata about the band.
@@ -114,7 +116,7 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
      */
     @Override
     protected GridCoverageResource createSource() throws DataStoreException {
-        final StorageConnector connector = new StorageConnector(getDataPath());
+        final var connector = new StorageConnector(storage);
         connector.setOption(OptionKey.COVERAGE_MODIFIER, this);
         return new GeoTiffStore(parent, parent.getProvider(), connector, 
true).components().get(0);
     }
@@ -223,17 +225,29 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
      */
     @Override
     public Optional<FileSet> getFileSet() throws DataStoreException {
-        return Optional.of(new FileSet(getDataPath()));
+        return Optional.of(new FileSet(storage));
     }
 
     /**
-     * Resolves path to image file.
+     * Sets the filename if it was not already set.
+     * The filename is rejected if not inside the scene directory.
      */
-    private Path getDataPath() {
-        if (parent.directory != null) {
-            return parent.directory.resolve(filename);
-        } else {
-            return Path.of(filename);
+    final void setFilename(final String value) {
+        if (storage == null) {
+            final Path base = parent.directory;
+            final Path file = (base != null ? base.resolve(value) : 
Path.of(value)).normalize();
+            if (base != null ? file.startsWith(base) : 
IOUtilities.isRelativeInsideDirectory(file)) {
+                storage = file;
+            } else {
+                
parent.warning(Resources.format(Resources.Keys.BandOutsideSceneDirectory_2, 
band.title, value));
+            }
         }
     }
+
+    /**
+     * Returns whether the mandatory properties have been defined on this band.
+     */
+    final boolean isValid() {
+        return storage != null;
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
index b2aadcec4c..a3bbe511f3 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
@@ -82,7 +82,7 @@ import org.apache.sis.util.collection.Containers;
  *
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   1.1
  */
 public class LandsatStore extends DataStore implements Aggregate {
@@ -143,6 +143,7 @@ public class LandsatStore extends DataStore implements 
Aggregate {
         connector.closeAllExcept(source);
         Path file = null;
         if (path != null) {
+            path = path.normalize();            // Needed for 
`Band.setFilename(String)`.
             if (source != null) {
                 file = path;
                 path = path.getParent();        // If the source has been 
opened, then the path is a file.
@@ -239,9 +240,9 @@ public class LandsatStore extends DataStore implements 
Aggregate {
              * The namespace of each identifier is the name of the data set 
directory.
              */
             resources = new Band[parser.bands.size()];
-            for (final Map.Entry<BandName,Band> entry : 
parser.bands.entrySet()) {
+            for (final Map.Entry<BandName, Band> entry : 
parser.bands.entrySet()) {
                 final Band component = entry.getValue();
-                if (component.filename != null) {
+                if (component.isValid()) {
                     component.identifier = factory.createLocalName(scope, 
entry.getKey().name());
                     resources[count++] = component;
                 }
@@ -321,6 +322,13 @@ public class LandsatStore extends DataStore implements 
Aggregate {
         return Optional.of(new FileSet(paths));
     }
 
+    /**
+     * Logs a warning.
+     */
+    final void warning(final String message) {
+        listeners.warning(message);
+    }
+
     /**
      * Closes this Landsat store and releases any underlying resources.
      * This method can be invoked asynchronously for interrupting a long 
reading process.
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
index 8fa79319a9..00293599ed 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStoreProvider.java
@@ -59,7 +59,7 @@ public class LandsatStoreProvider extends DataStoreProvider {
      *
      * @see #getLogger()
      */
-    private static final Logger LOGGER = 
Logger.getLogger("org.apache.sis.storage.landsat");
+    static final Logger LOGGER = 
Logger.getLogger("org.apache.sis.storage.landsat");
 
     /**
      * The parameter descriptor to be returned by {@link #getOpenParameters()}.
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
index 348fa06f0b..11d3bfc112 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
@@ -573,7 +573,7 @@ final class MetadataReader extends MetadataBuilder {
              * This parameter is only present if the band is included in the 
product.
              */
             case "FILE_NAME_BAND_": {
-                band(key, band).ifPresent((b) -> b.filename = value);
+                band(key, band).ifPresent((b) -> b.setFilename(value));
                 break;
             }
             /*
@@ -913,7 +913,7 @@ final class MetadataReader extends MetadataBuilder {
         final var content = (DefaultCoverageDescription) 
Containers.peekIfSingleton(result.getContentInfo());
         if (content != null) {
             final var groups = new 
EnumMap<BandGroupName,DefaultAttributeGroup>(BandGroupName.class);
-            for (final EnumMap.Entry<BandName,Band> entry : bands.entrySet()) {
+            for (final EnumMap.Entry<BandName, Band> entry : bands.entrySet()) 
{
                 final DefaultAttributeGroup g = 
groups.computeIfAbsent(entry.getKey().group, (k) -> {
                     var ag = new 
DefaultAttributeGroup(CoverageContentType.PHYSICAL_MEASUREMENT, null);
                     content.getAttributeGroups().add(ag);
@@ -952,7 +952,7 @@ final class MetadataReader extends MetadataBuilder {
         if (key != null) {
             String file = getFilename();
             if (reader instanceof LineNumberReader) {
-                file = file + ":" + ((LineNumberReader) 
reader).getLineNumber();
+                file = file + ':' + ((LineNumberReader) 
reader).getLineNumber();
             }
             key = errors().getString(Errors.Keys.CanNotReadPropertyInFile_2, 
toLongName(key), file);
         }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.java
new file mode 100644
index 0000000000..392c0e7a29
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.landsat.internal;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import org.apache.sis.util.resources.KeyConstants;
+import org.apache.sis.util.resources.IndexedResourceBundle;
+
+
+/**
+ * Warning and error messages that are specific to the Landsat module.
+ * Resources in this file should not be used by any other module.
+ * For resources shared by many modules in the Apache <abbr>SIS</abbr> project,
+ * see the {@code org.apache.sis.util.resources} package.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ */
+public class Resources extends IndexedResourceBundle {
+    /**
+     * Resource keys. This class is used when compiling sources, but no 
dependencies to
+     * {@code Keys} should appear in any resulting class files. Since the Java 
compiler
+     * inlines final integer values, using long identifiers will not bloat the 
constant
+     * pools of compiled classes.
+     *
+     * @author  Martin Desruisseaux (IRD, Geomatys)
+     */
+    public static final class Keys extends KeyConstants {
+        /**
+         * The unique instance of key constants handler.
+         */
+        static final Keys INSTANCE = new Keys();
+
+        /**
+         * For {@link #INSTANCE} creation only.
+         */
+        private Keys() {
+        }
+
+        /**
+         * Returns the value of a field declared in this {@code Keys} class.
+         * This method is needed for encapsulation reason, because classes in
+         * other modules cannot access this class even by reflection.
+         */
+        @Override
+        protected Object getStaticValue(final Field field) throws 
IllegalAccessException {
+            if (field.getDeclaringClass() == Keys.class) {
+                return field.get(null);
+            }
+            throw new IllegalAccessException();
+        }
+
+        /**
+         * File “{1}” of band “{0}” is outside the scene directory.
+         */
+        public static final short BandOutsideSceneDirectory_2 = 1;
+    }
+
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources() {
+    }
+
+    /**
+     * Opens the binary file containing the localized resources to load.
+     * This method delegates to {@link Class#getResourceAsStream(String)},
+     * but this delegation must be done from the same module as the one
+     * that provides the binary file.
+     */
+    @Override
+    protected InputStream getResourceAsStream(final String name) {
+        return getClass().getResourceAsStream(name);
+    }
+
+    /**
+     * Returns the handle for the {@code Keys} constants.
+     *
+     * @return a handler for the constants declared in the inner {@code Keys} 
class.
+     */
+    @Override
+    protected KeyConstants getKeyConstants() {
+        return Keys.INSTANCE;
+    }
+
+    /**
+     * Returns resources in the given locale.
+     *
+     * @param  locale  the locale, or {@code null} for the default locale.
+     * @return resources in the given locale.
+     * @throws MissingResourceException if resources cannot be found.
+     */
+    public static Resources forLocale(final Locale locale) {
+        /*
+         * We cannot factorize this method into the parent class, because we 
need to call
+         * `ResourceBundle.getBundle(String)` from the module that provides 
the resources.
+         * We do not cache the result because `ResourceBundle` already 
provides a cache.
+         */
+        return (Resources) getBundle(Resources.class.getName(), 
nonNull(locale));
+    }
+
+    /**
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @param  arg1  value to substitute to "{1}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be 
found.
+     */
+    public static String format(final short  key,
+                                final Object arg0,
+                                final Object arg1)
+    {
+        return forLocale(null).getString(key, arg0, arg1);
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.properties
new file mode 100644
index 0000000000..6bcdbc68f4
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources.properties
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Resources in this file are for Landsat module usage only and should not be 
used by any other module.
+# For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
+#
+BandOutsideSceneDirectory_2 = File \u201c{1}\u201d of band \u201c{0}\u201d is 
outside the scene directory.
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_en.java
similarity index 62%
copy from 
endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
copy to 
endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_en.java
index 37a0ea58ef..a2d900e951 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_en.java
@@ -14,22 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.storage.landsat.internal;
+
 
 /**
- * Earth observation stores.
- *
- * @author  Rémi Maréchal (Geomatys)
- * @author  Thi Phuong Hao Nguyen (VNSC)
- * @author  Minh Chinh Vu (VNSC)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- * @since   0.8
+ * Resource in English language.
  */
-module org.apache.sis.storage.earthobservation {
-    requires transitive org.apache.sis.storage.geotiff;
-
-    provides org.apache.sis.storage.DataStoreProvider
-        with org.apache.sis.storage.landsat.LandsatStoreProvider;
-
-    exports org.apache.sis.storage.landsat;
+public class Resources_en extends Resources {
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources_en() {
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.java
similarity index 62%
copy from 
endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
copy to 
endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.java
index 37a0ea58ef..1b0e5c6449 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.java
@@ -14,22 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.storage.landsat.internal;
+
 
 /**
- * Earth observation stores.
- *
- * @author  Rémi Maréchal (Geomatys)
- * @author  Thi Phuong Hao Nguyen (VNSC)
- * @author  Minh Chinh Vu (VNSC)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- * @since   0.8
+ * Messages in French language.
  */
-module org.apache.sis.storage.earthobservation {
-    requires transitive org.apache.sis.storage.geotiff;
-
-    provides org.apache.sis.storage.DataStoreProvider
-        with org.apache.sis.storage.landsat.LandsatStoreProvider;
-
-    exports org.apache.sis.storage.landsat;
+public class Resources_fr extends Resources {
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources_fr() {
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.properties
new file mode 100644
index 0000000000..ecaeed58dc
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/Resources_fr.properties
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Resources in this file are for Landsat module usage only and should not be 
used by any other module.
+# For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
+#
+# Punctuation rules in French (source: 
http://unicode.org/udhr/n/notes_fra.html)
+#
+#   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
+#   U+00A0 NO-BREAK SPACE         before  :
+#
+BandOutsideSceneDirectory_2 = Le fichier \u00ab\u202f{1}\u202f\u00bb de la 
bande \u00ab\u202f{0}\u202f\u00bb est en dehors du dossier de la sc\u00e8ne.
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/package-info.java
similarity index 65%
copy from 
endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
copy to 
endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/package-info.java
index 37a0ea58ef..075ca5ba26 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/module-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/internal/package-info.java
@@ -16,20 +16,13 @@
  */
 
 /**
- * Earth observation stores.
+ * Shared classes for the implementation of Landsat reader.
+ *
+ * <STRONG>Do not use!</STRONG>
+ *
+ * This package is for internal use by Apache <abbr>SIS</abbr> only.
+ * Classes in this package may change in incompatible ways in any future 
version without notice.
  *
- * @author  Rémi Maréchal (Geomatys)
- * @author  Thi Phuong Hao Nguyen (VNSC)
- * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- * @since   0.8
  */
-module org.apache.sis.storage.earthobservation {
-    requires transitive org.apache.sis.storage.geotiff;
-
-    provides org.apache.sis.storage.DataStoreProvider
-        with org.apache.sis.storage.landsat.LandsatStoreProvider;
-
-    exports org.apache.sis.storage.landsat;
-}
+package org.apache.sis.storage.landsat.internal;
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/package-info.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/package-info.java
index 51af8892be..bed1f0742a 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/package-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/package-info.java
@@ -27,7 +27,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   1.1
  */
 package org.apache.sis.storage.landsat;
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/LandsatStoreProviderTest.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/LandsatStoreProviderTest.java
index e00d32e595..d976dfac47 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/LandsatStoreProviderTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/LandsatStoreProviderTest.java
@@ -16,7 +16,15 @@
  */
 package org.apache.sis.storage.landsat;
 
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Files;
 import java.nio.charset.StandardCharsets;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.OptionKey;
 import org.apache.sis.storage.ProbeResult;
 import org.apache.sis.storage.StorageConnector;
@@ -24,8 +32,10 @@ import org.apache.sis.storage.DataStoreException;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.TestCase;
+import static org.apache.sis.test.Assertions.assertSingleton;
+import org.apache.sis.test.TestCaseWithLogs;
 
 
 /**
@@ -33,11 +43,13 @@ import org.apache.sis.test.TestCase;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class LandsatStoreProviderTest extends TestCase {
+@SuppressWarnings("exports")
+public final class LandsatStoreProviderTest extends TestCaseWithLogs {
     /**
      * Creates a new test case.
      */
     public LandsatStoreProviderTest() {
+        super(LandsatStoreProvider.LOGGER);
     }
 
     /**
@@ -47,9 +59,65 @@ public final class LandsatStoreProviderTest extends TestCase 
{
      */
     @Test
     public void testProbeContentFromReader() throws DataStoreException {
-        final StorageConnector connector = new 
StorageConnector(MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"));
+        final var connector = new 
StorageConnector(MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"));
         connector.setOption(OptionKey.ENCODING, StandardCharsets.UTF_8);
-        final LandsatStoreProvider provider = new LandsatStoreProvider();
+        final var provider = new LandsatStoreProvider();
         assertEquals(ProbeResult.SUPPORTED, provider.probeContent(connector));
+        loggings.assertNoUnexpectedLog();
+    }
+
+    /**
+     * Creates a temporary file with one band and read it.
+     * The path of the image for the single band is returned.
+     *
+     * @param  tmpDir     temporary directory where to write a scene.
+     * @param  sceneName  name of the scene. Will be the sub-directory 
filename.
+     * @param  tiffFile   path of the <abbr>TIFF</abbr> file.
+     * @return paths of the file as provided by the resource.
+     * @throws IOException if an error occurred while writing the temporary 
file.
+     * @throws DataStoreException if an error occurred while reading the 
temporary file.
+     */
+    private static Collection<Path> readSingleBand(final Path tmpDir, final 
String sceneName, final String tiffFile)
+            throws IOException, DataStoreException
+    {
+        final Path sceneDir = 
Files.createDirectories(tmpDir.resolve(sceneName));
+        final Path sceneFile = sceneDir.resolve(sceneName + "_MTL.txt");
+        Files.write(sceneFile, Arrays.asList(
+                "GROUP = LANDSAT_METADATA_FILE",
+                "  GROUP = PRODUCT_CONTENTS",
+                "    FILE_NAME_BAND_1 = \"" + tiffFile + "\"",
+                "  END_GROUP = PRODUCT_CONTENTS",
+                "END_GROUP = LANDSAT_METADATA_FILE",
+                "END"));
+
+        final var paths = new ArrayList<Path>();
+        try (var store = new LandsatStore(null, new 
StorageConnector(sceneDir))) {
+            for (Resource component : store.components()) {
+                Aggregate group = assertInstanceOf(Aggregate.class, component);
+                Resource band = assertSingleton(group.components());
+                paths.addAll(band.getFileSet().orElseThrow().getPaths());
+            }
+        }
+        return paths;
+    }
+
+    /**
+     * Verifies that the Landsat reader detects when a band path is outside 
the scene directory.
+     *
+     * @param  tmpDir  temporary directory where to write a scene.
+     * @throws IOException if an error occurred while writing the temporary 
file.
+     * @throws DataStoreException if an error occurred while reading the 
temporary file.
+     */
+    @Test
+    public void testBandPathValidation(@TempDir final Path tmpDir)
+            throws IOException, DataStoreException
+    {
+        final Path expected = tmpDir.resolve("valid", "B1.TIFF");
+        final Path actual = assertSingleton(readSingleBand(tmpDir, "valid", 
"B1.TIFF"));
+        assertEquals(expected.toAbsolutePath(), actual.toAbsolutePath());
+        loggings.assertNoUnexpectedLog();
+        assertTrue(readSingleBand(tmpDir, "invalid", 
"../outside/secret.tiff").isEmpty());
+        loggings.assertNextLogContains("../outside/secret.tiff", "Coastal 
Aerosol");
+        loggings.assertNoUnexpectedLog();
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
index bc1b7c8544..795682b9f2 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -28,6 +28,7 @@ import static java.util.Map.entry;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
 import org.opengis.metadata.Metadata;
@@ -45,6 +46,7 @@ import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.FactoryException;
 import org.apache.sis.storage.AbstractResource;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.event.StoreListeners;
 import org.opengis.test.dataset.ContentVerifier;
 
@@ -55,6 +57,7 @@ import org.opengis.test.dataset.ContentVerifier;
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class MetadataReaderTest extends TestCase {
     /**
      * Helper class for verifying metadata content.
@@ -99,7 +102,9 @@ public final class MetadataReaderTest extends TestCase {
         try (BufferedReader in = new BufferedReader(new InputStreamReader(
                 
MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
         {
-            final MetadataReader reader = new MetadataReader(null, 
"LandsatTest.txt", createListeners());
+            final var store = new LandsatStore(null, new StorageConnector(
+                    new StringReader("GROUP = LANDSAT_METADATA_FILE\nEND")));
+            final var reader = new MetadataReader(store, "LandsatTest.txt", 
createListeners());
             reader.read(in);
             actual = reader.getMetadata();
         }
diff --git a/endorsed/src/org.apache.sis.storage/main/module-info.java 
b/endorsed/src/org.apache.sis.storage/main/module-info.java
index baf79ec7e3..024b42f67a 100644
--- a/endorsed/src/org.apache.sis.storage/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage/main/module-info.java
@@ -84,6 +84,7 @@ module org.apache.sis.storage {
             org.apache.sis.storage.netcdf,
             org.apache.sis.storage.geoheif,
             org.apache.sis.storage.geotiff,
+            org.apache.sis.storage.earthobservation,
             org.apache.sis.storage.json,                // In the "incubator" 
sub-project.
             org.apache.sis.storage.shapefile,           // In the "incubator" 
sub-project.
             org.apache.sis.storage.geopackage,          // In the "incubator" 
sub-project.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
index b2d2ece27e..cbe5b8c131 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
@@ -559,6 +559,23 @@ check:  if (stream instanceof ChannelData) {
         return false;
     }
 
+    /**
+     * Returns {@code true} if the given file is relative and does not go up 
in the parent directory.
+     * Callers should invoke {@link Path#normalize()} before this method.
+     *
+     * @param  path  the path to test.
+     * @return whether the given path is relative and does not go up in the 
parent directory.
+     */
+    public static boolean isRelativeInsideDirectory(final Path path) {
+        if (path.isAbsolute()) {
+            return false;
+        }
+        if (path.getNameCount() != 0 && 
path.getName(0).toString().startsWith(".")) {
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Returns {@code true} if the file at the specified path is absent or an 
empty file.
      * If the file exists but is not a regular file, then this method returns 
{@code false}.

Reply via email to