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 8818503612 During XML unmarshalling, if a `xlink:href` is not local 
(i.e. starts with `#`), resolve only if explicitely authorized by the user. 
Otherwise leave the element empty.
8818503612 is described below

commit 881850361234403ddbbaa5faa8456a114ce2903f
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Sep 15 19:39:24 2026 +0900

    During XML unmarshalling, if a `xlink:href` is not local (i.e. starts with 
`#`),
    resolve only if explicitely authorized by the user. Otherwise leave the 
element empty.
---
 .../apache/sis/metadata/internal/Resources.java    |  5 ++
 .../sis/metadata/internal/Resources.properties     |  1 +
 .../sis/metadata/internal/Resources_fr.properties  |  1 +
 .../main/org/apache/sis/xml/IdentifierSpace.java   |  4 +-
 .../main/org/apache/sis/xml/ReferenceResolver.java | 74 ++++++++++++++++++----
 .../main/org/apache/sis/xml/XML.java               | 28 ++++----
 .../apache/sis/xml/internal/shared/URISource.java  | 12 ++--
 .../main/org/apache/sis/xml/package-info.java      |  2 +-
 .../org/apache/sis/metadata/xml/TestUsingFile.java | 14 ++++
 .../org/apache/sis/xml/ReferenceResolverMock.java  |  1 +
 .../org/apache/sis/xml/ReferenceResolverTest.java  | 68 ++++++++++++++++++--
 .../org/apache/sis/xml/bind/referencing/Code.java  |  2 +-
 12 files changed, 173 insertions(+), 39 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
index b9cd253db8..ef63d47380 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
@@ -67,6 +67,11 @@ public class Resources extends IndexedResourceBundle {
             throw new IllegalAccessException();
         }
 
+        /**
+         * References to external documents are not followed without explicit 
authorization.
+         */
+        public static final short AuthorizationRequired = 10;
+
         /**
          * Cannot handle `{1}` as a type derived from the {0} standard.
          */
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
index b20abf46fc..00772d8d87 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
@@ -19,6 +19,7 @@
 # Resources in this file are for `org.apache.sis.metadata` 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.
 #
+AuthorizationRequired             = References to external documents are not 
followed without explicit authorization.
 CannotHandleAsStandardType_2      = Cannot handle `{1}` as a type derived from 
the {0} standard.
 ConnectionAlreadyInitialized_1    = Connection to \u201c{0}\u201d database is 
already initialized.
 ElementAlreadyInitialized_1       = This metadata element is already 
initialized with value \u201c{0}\u201d.
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
index 12e98cbee0..5cbbad614a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
@@ -24,6 +24,7 @@
 #   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
 #   U+00A0 NO-BREAK SPACE         before  :
 #
+AuthorizationRequired             = Les r\u00e9f\u00e9rences vers des 
documents externes ne sont pas suivit sans autorisation explicite.
 CannotHandleAsStandardType_2      = Ne peut pas g\u00e9rer `{1}` comme un type 
d\u00e9riv\u00e9 du standard {0}.
 ConnectionAlreadyInitialized_1    = La connexion \u00e0 la base de 
donn\u00e9es \u00ab\u202f{0}\u202f\u00bb est d\u00e9j\u00e0 initialis\u00e9e.
 ElementAlreadyInitialized_1       = Cet \u00e9l\u00e9ment de 
m\u00e9ta-donn\u00e9e est d\u00e9j\u00e0 initialis\u00e9 avec la valeur 
\u00ab\u202f{0}\u202f\u00bb.
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/IdentifierSpace.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/IdentifierSpace.java
index 69a865a7cf..4c192cd239 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/IdentifierSpace.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/IdentifierSpace.java
@@ -82,8 +82,8 @@ public interface IdentifierSpace<T> extends Citation {
      * is <i>unqualified</i>).</p>
      *
      * <p>Elements with {@code gco:uuid} attribute can be referenced from 
other XML elements using the
-     * {@code gco:uuidref} attribute. However, this is not done automatically 
by Apache SIS. Users need
-     * to manage their set of UUIDs in their own {@link ReferenceResolver} 
subclass.</p>
+     * {@code gco:uuidref} attribute. However, this is not done automatically 
by Apache <abbr>SIS</abbr>.
+     * Users need to manage their set of UUIDs in their own {@link 
ReferenceResolver} subclass.</p>
      *
      * @see UUID
      */
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java
index 47f2b26da1..2ca93e5608 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java
@@ -18,6 +18,7 @@ package org.apache.sis.xml;
 
 import java.net.URI;
 import java.util.UUID;
+import java.nio.file.AccessDeniedException;
 import java.lang.reflect.Proxy;
 import javax.xml.transform.Source;
 import javax.xml.transform.URIResolver;
@@ -46,20 +47,39 @@ import org.apache.sis.xml.internal.shared.XmlUtilities;
  * to a unmarshaller.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.7
  * @since   0.3
  */
 public class ReferenceResolver {
     /**
-     * The default and thread-safe instance. This instance is used at 
unmarshalling time when
-     * no {@code ReferenceResolver} was explicitly set by the {@link 
XML#RESOLVER} property.
+     * The default resolved used at unmarshalling time when no resolver was 
explicitly set.
+     * This instance resolves {@code xlink:href} which are <abbr>URI</abbr> 
fragments relative
+     * to the current document, but does not accept to open references to 
external documents.
+     *
+     * @see XML#RESOLVER
      */
     public static final ReferenceResolver DEFAULT = new ReferenceResolver();
 
+    /**
+     * A resolver which accepts to open all external documents referenced by 
{@code xlink:href}.
+     * By {@linkplain #DEFAULT default}, only <abbr>URI</abbr> fragments 
relative to the current document are opened.
+     * But if this resolver is specified as a {@link XML#RESOLVER} property, 
all <abbr>URI</abbr>s will be accepted.
+     *
+     * <p><b>Historical note:</b> this was the default behavior in Apache 
<abbr>SIS</abbr> 1.5 and 1.6, but
+     * <abbr>SIS</abbr> 1.7 reverted to not opening external document by 
default for security reasons.</p>
+     *
+     * @see XML#RESOLVER
+     * @see #canOpenExternal(URI)
+     *
+     * @since 1.7
+     */
+    public static final ReferenceResolver OPEN_EXTERNAL_DOCUMENTS = new 
ReferenceResolver();
+
     /**
      * Provider of sources to use for unmarshalling objects referenced by 
links to another document.
      * It provides the {@code source} argument in {@link 
#resolveExternal(MarshalContext, Source)}.
-     * If {@code null}, a default resolution is done.
+     * If {@code null}, relative <abbr>URI</abbr>s will be {@linkplain 
URI#resolve(URI) resolved}
+     * against the <abbr>URI</abbr> of the document which contains the 
reference.
      *
      * @since 1.5
      */
@@ -255,7 +275,13 @@ public class ReferenceResolver {
      * </ul>
      * The resolved URL, if known, should be available in {@link 
Source#getSystemId()}.
      *
-     * <h4>Error handling</h4>
+     * <h4>Authorization to resolve {@code xlink:href}</h4>
+     * If the given {@code source} argument wraps an {@link URI}, then this 
method asks to
+     * {@link #canOpenExternal(URI)} whether this {@code ReferenceResolver} 
can open that <abbr>URI</abbr>.
+     * If {@code canOpenExternal(…)} returns {@code false}, then an {@link 
AccessDeniedException} is thrown.
+     * For security reasons, the default {@code canOpenExternal(…)} 
implementation returns always {@code false}.
+     *
+     * <h4>Error handling on failure to resolve {@code xlink:href}</h4>
      * The default implementation keeps a cache during the execution of an 
{@code XML.unmarshall(…)} method
      * (or actually, during a {@linkplain MarshallerPool pooled unmarshaller} 
method).
      * If an exception is thrown during the document unmarshalling, this 
failure is also recorded in the cache.
@@ -271,12 +297,16 @@ public class ReferenceResolver {
      *
      * @since 1.5
      */
+    @SuppressWarnings("UseSpecificCatch")
     protected Object resolveExternal(final MarshalContext context, final 
Source source) throws Exception {
         final Object document;
         final String fragment;
         final URI uri;
         if (source instanceof URISource) {
             final var s = (URISource) source;
+            if (!canOpenExternal(s.document)) {
+                throw new AccessDeniedException(s.document.toString());
+            }
             uri = s.getReadableURI();
             document = s.document;
             fragment = s.fragment;
@@ -347,9 +377,26 @@ public class ReferenceResolver {
         return object;
     }
 
+    /**
+     * Returns whether the given external document referenced in a {@code 
xlink:href} can be opened.
+     * If this method returns {@code false}, then {@link 
#resolveExternal(MarshalContext, Source)}
+     * while throw an {@link AccessDeniedException}.
+     * The {@linkplain #DEFAULT default} implementation returns {@code false}.
+     *
+     * @param  document  the external document referenced in a {@code 
xlink:href}.
+     * @return whether the given document can be opened.
+     *
+     * @see #OPEN_EXTERNAL_DOCUMENTS
+     *
+     * @since 1.7
+     */
+    public boolean canOpenExternal(URI document) {
+        return this == OPEN_EXTERNAL_DOCUMENTS;
+    }
+
     /**
      * Returns {@code true} if the marshaller can use a {@code 
xlink:href="#id"} reference to the given object
-     * instead of writing the full XML element. This method is invoked by the 
marshaller when:
+     * instead of writing the full <abbr>XML</abbr> element. This method is 
invoked by the marshaller when:
      *
      * <ul>
      *   <li>The given object has already been marshalled in the same XML 
document.</li>
@@ -380,15 +427,16 @@ public class ReferenceResolver {
      *
      * @since 0.7
      */
-    public <T> boolean canSubstituteByReference(final MarshalContext context, 
final Class<T> type, final T object, final String id) {
+    public <T> boolean canSubstituteByReference(MarshalContext context, 
Class<T> type, T object, String id) {
         return true;
     }
 
     /**
      * Returns {@code true} if the marshaller can use a reference to the given 
object
-     * instead of writing the full XML element. This method is invoked when an 
object to
-     * be marshalled has a UUID identifier. Because those object may be 
defined externally,
-     * SIS cannot know if the object shall be fully marshalled or not.
+     * instead of writing the full <abbr>XML</abbr> element.
+     * This method is invoked when an object to be marshalled has a 
<abbr>UUID</abbr> identifier.
+     * Because those object may be defined externally,
+     * <abbr>SIS</abbr> cannot know if the object shall be fully marshalled or 
not.
      * Such information needs to be provided by the application.
      *
      * <p>The default implementation returns {@code true} in the following 
cases:</p>
@@ -407,7 +455,7 @@ public class ReferenceResolver {
      * @return {@code true} if the marshaller can use the {@code uuidref} 
attribute
      *         instead of marshalling the given object.
      */
-    public <T> boolean canSubstituteByReference(final MarshalContext context, 
final Class<T> type, final T object, final UUID uuid) {
+    public <T> boolean canSubstituteByReference(MarshalContext context, 
Class<T> type, T object, UUID uuid) {
         return (object instanceof NilObject) || (object instanceof Emptiable 
&& ((Emptiable) object).isEmpty());
     }
 
@@ -434,7 +482,7 @@ public class ReferenceResolver {
      * @return {@code true} if the marshaller can use the {@code xlink:href} 
attribute
      *         instead of marshalling the given object.
      */
-    public <T> boolean canSubstituteByReference(final MarshalContext context, 
final Class<T> type, final T object, final XLink link) {
+    public <T> boolean canSubstituteByReference(MarshalContext context, 
Class<T> type, T object, XLink link) {
         return (object instanceof NilObject) || (object instanceof Emptiable 
&& ((Emptiable) object).isEmpty());
     }
 
@@ -469,7 +517,7 @@ public class ReferenceResolver {
      * @param  text     the textual representation of the value for which to 
get the anchor.
      * @return the anchor for the given text, or {@code null} if none.
      */
-    public XLink anchor(final MarshalContext context, final Object value, 
final CharSequence text) {
+    public XLink anchor(MarshalContext context, Object value, CharSequence 
text) {
         return (text instanceof Anchor) ? (Anchor) text : null;
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
index e196954ddb..a63e299a53 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XML.java
@@ -251,28 +251,32 @@ public final class XML {
     public static final String LENIENT_UNMARSHAL = 
"org.apache.sis.xml.lenient";
 
     /**
-     * Allows client code to replace {@code xlink} or {@code uuidref} 
attributes by the actual objects to use.
+     * Allows client code to replace {@code xlink} or {@code uuidref} 
attributes by the actual data.
      * The value for this property shall be an instance of {@link 
ReferenceResolver}.
-     *
-     * <p>If a property in a XML document is defined only by {@code xlink} or 
{@code uuidref} attributes,
-     * without any concrete definition, then the default behavior is as 
below:</p>
+     * The specified reference resolver (of if none, the {@linkplain 
ReferenceResolver#DEFAULT default} one)
+     * is used when a <abbr>XML</abbr> element is defined only by {@code 
xlink} or {@code uuidref} attributes,
+     * without any concrete definition. The typical choices are:
      *
      * <ul>
      *   <li>If the reference is of the form {@code xlink:href="#foo"} and an 
object with the {@code gml:id="foo"}
-     *       attribute was previously found in the same XML document, then 
that object will be used.</li>
-     *   <li>Otherwise, if {@code xlink:href} references an external document, 
that document is unmarshalled.
-     *       The URI resolution can be controlled with an {@link 
javax.xml.transform.URIResolver} specified
-     *       at construction time.</li>
+     *       attribute was previously found in the same <abbr>XML</abbr> 
document, then that object will be used.</li>
+     *   <li>Otherwise, if {@code xlink:href} references an external document 
and the resolver is
+     *       {@linkplain ReferenceResolver#canOpenExternal(java.net.URI) 
authorized to open external documents},
+     *       then that document is unmarshalled.</li>
      *   <li>Otherwise, an empty element containing only the values of the 
above-cited attributes is created.</li>
      * </ul>
      *
-     * Applications can sometimes do better by using some domain-specific 
knowledge, for example by searching in a
-     * database. Users can define their search algorithm by subclassing {@link 
ReferenceResolver} and configuring
+     * A custom {@code ReferenceResolver} can be specified for controlling 
{@code xlink:href} handling.
+     * For example, the resolution of <abbr>URI</abbr>s relatively to the base 
document can be controlled
+     * with an {@link javax.xml.transform.URIResolver} specified to the {@link 
ReferenceResolver} constructor.
+     * Other methods can also be overridden for using some domain-specific 
knowledge,
+     * for example by searching in a database the value associated to specific 
{@code xlink:href} values.
+     * Users can define their search algorithm by subclassing {@link 
ReferenceResolver} and configuring
      * a unmarshaller as below:
      *
      * {@snippet lang="java" :
-     *     ReferenceResolver  myResolver = ...;
-     *     Map<String,Object> properties = new HashMap<>();
+     *     var myResolver = new ReferenceResolver(...);
+     *     var properties = new HashMap<String, Object>();
      *     properties.put(XML.RESOLVER, myResolver);
      *     Object obj = XML.unmarshal(source, properties);
      *     }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/internal/shared/URISource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/internal/shared/URISource.java
index 9928d89dfa..51cc351f2a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/internal/shared/URISource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/internal/shared/URISource.java
@@ -54,9 +54,10 @@ public final class URISource extends StreamSource {
      */
     URISource(URI source) throws URISyntaxException {
         source = source.normalize();
+        fragment = Strings.trimOrNull(source.getFragment());
         // Build a new URI unconditionally because it also decodes escaped 
characters.
-        URI c = new URI(source.getScheme(), source.getSchemeSpecificPart(), 
null);
-        if (c.isOpaque() && "file".equalsIgnoreCase(c.getScheme())) {
+        source = new URI(source.getScheme(), source.getSchemeSpecificPart(), 
null);
+        if (source.isOpaque() && "file".equalsIgnoreCase(source.getScheme())) {
             /*
              * If the URI is "file:something" without "/" or "///" characters, 
resolve as an absolute path.
              * This special case happens if `IOUtilities.toFileOrURI(String)` 
did not converted a string to
@@ -64,10 +65,9 @@ public final class URISource extends StreamSource {
              * we can now attempt this conversion again. The result will be an 
absolute path. This is needed
              * for `URI.resolve(URI)` to work.
              */
-            c = new File(c.getSchemeSpecificPart()).toURI();
+            source = new File(source.getSchemeSpecificPart()).toURI();
         }
-        document = source.equals(c) ? source : c;       // Share the existing 
instance if applicable.
-        fragment = Strings.trimOrNull(source.getFragment());
+        document = source;
     }
 
     /**
@@ -116,6 +116,8 @@ public final class URISource extends StreamSource {
 
     /**
      * Returns a string representation of this source for debugging purposes.
+     *
+     * @return string representation for debugging purposes.
      */
     @Override
     public String toString() {
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
index eae744a91e..683eaf3014 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
@@ -59,7 +59,7 @@
  * @author  Guilhem Legal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Cullen Rombach (Image Matters)
- * @version 1.5
+ * @version 1.7
  * @since   0.3
  */
 package org.apache.sis.xml;
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/TestUsingFile.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/TestUsingFile.java
index ee49ef3c34..c20081f7b9 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/TestUsingFile.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/TestUsingFile.java
@@ -18,7 +18,10 @@ package org.apache.sis.metadata.xml;
 
 import java.net.URL;
 import java.io.InputStream;
+import java.net.URISyntaxException;
+import javax.xml.transform.Source;
 import org.apache.sis.util.Version;
+import org.apache.sis.xml.internal.shared.URISource;
 
 // Test dependencies
 import org.apache.sis.xml.test.TestCase;
@@ -54,6 +57,17 @@ public abstract class TestUsingFile extends TestCase {
             this.directory = directory;
         }
 
+        /**
+         * Returns the source to the specified XML file.
+         *
+         * @param  filename  the XML file in the directory represented by this 
enumeration.
+         * @return source for the specified file.
+         * @throws URISyntaxException if the URL to the file is not valid.
+         */
+        public final Source getSource(final String filename) throws 
URISyntaxException {
+            return URISource.create(null, getURL(filename).toURI());
+        }
+
         /**
          * Returns the URL to the specified XML file.
          *
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverMock.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverMock.java
index 90bd05afa8..281bd34db8 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverMock.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverMock.java
@@ -53,6 +53,7 @@ public final class ReferenceResolverMock extends 
ReferenceResolver {
      * @param  marshalling {@code true} for marshalling, or {@code false} for 
unmarshalling.
      * @return the (un)marshalling context.
      */
+    @SuppressWarnings("exports")
     public static Context begin(final boolean marshalling) {
         return new Context(marshalling ? Context.MARSHALLING : 0, null, null, 
null, null, null, null,
                 null, new ReferenceResolverMock(), null, null);
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
index 5fc6ef730b..99e672df7f 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
@@ -16,7 +16,13 @@
  */
 package org.apache.sis.xml;
 
+import java.util.HashMap;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
 import java.io.IOException;
+import java.nio.file.AccessDeniedException;
+import java.net.URISyntaxException;
+import javax.xml.transform.Source;
 import jakarta.xml.bind.JAXBException;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.DataIdentification;
@@ -35,23 +41,76 @@ import static 
org.apache.sis.test.Assertions.assertSingleton;
  * @author  Martin Desruisseaux (Geomatys)
  */
 @SuppressWarnings("exports")
-public final class ReferenceResolverTest extends TestUsingFile {
+public final class ReferenceResolverTest extends TestUsingFile implements 
Filter {
+    /**
+     * Number of times that an "access denied" warning is expected.
+     */
+    private int expectAccessDenied;
+
     /**
      * Creates a new test case.
      */
     public ReferenceResolverTest() {
     }
 
+    /**
+     * Invoked when a warning occurred during the XML unmarshalling.
+     *
+     * @param  record  the warning.
+     * @return always {@code false} for keeping the output console quieter.
+     */
+    @Override
+    public boolean isLoggable(final LogRecord record) {
+        assertNotEquals(0, expectAccessDenied);
+        String file = assertInstanceOf(AccessDeniedException.class, 
record.getThrown()).getFile();
+        assertTrue(file.endsWith("Citation.xml"), file);
+        expectAccessDenied--;
+        return false;
+    }
+
+    /**
+     * Reads the test <abbr>XML</abbr> document.
+     *
+     * @param  readExternal  whether to allow the reading of external 
documents.
+     */
+    private DataIdentification data(final boolean readExternal) throws 
URISyntaxException, JAXBException {
+        final Source source = 
Format.XML2016.getSource("UsingExternalXLink.xml");
+        final var properties = new HashMap<String, Object>(4);
+        assertNull(properties.put(XML.WARNING_FILTER, this));
+        if (readExternal) {
+            assertNull(properties.put(XML.RESOLVER, 
ReferenceResolver.OPEN_EXTERNAL_DOCUMENTS));
+        }
+        final var data = assertInstanceOf(DataIdentification.class, 
XML.unmarshal(source, properties));
+        assertEquals("Test the use of XLink to an external document.", 
data.getAbstract().toString());
+        return data;
+    }
+
+    /**
+     * Tests that the attempt to read a fragment in an external document is 
denied by default.
+     *
+     * @throws URISyntaxException if an error occurred while getting the URL 
to the test file.
+     * @throws IOException if an error occurred while opening the test file.
+     * @throws JAXBException if an error occurred while parsing the test file.
+     */
+    @Test
+    public void testAccessDenied() throws URISyntaxException, IOException, 
JAXBException {
+        expectAccessDenied = 2;
+        final DataIdentification data = data(false);
+        final Citation citation = data.getCitation();
+        assertNull(citation.getTitle());
+        assertEquals(0, expectAccessDenied, "Expected a warning.");
+    }
+
     /**
      * Tests loading a document with a {@code xlink:href} to an external 
document.
      *
+     * @throws URISyntaxException if an error occurred while getting the URL 
to the test file.
      * @throws IOException if an error occurred while opening the test file.
      * @throws JAXBException if an error occurred while parsing the test file.
      */
     @Test
-    public void testUsingExternalXLink() throws IOException, JAXBException {
-        final var data = (DataIdentification) 
XML.unmarshal(Format.XML2016.getURL("UsingExternalXLink.xml"));
-        assertEquals("Test the use of XLink to an external document.", 
data.getAbstract().toString());
+    public void testUsingExternalXLink() throws URISyntaxException, 
IOException, JAXBException {
+        final DataIdentification data = data(true);
         final Citation citation = data.getCitation();
         DefaultCitationTest.verifyUnmarshalledCitation(citation);
         /*
@@ -62,6 +121,5 @@ public final class ReferenceResolverTest extends 
TestUsingFile {
         assertEquals("Little John", reusing.getName().toString());
         assertSame(assertSingleton(parent .getContactInfo()),
                    assertSingleton(reusing.getContactInfo()));
-
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
index 4e08c1bfc3..8aee2eb65e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
@@ -19,9 +19,9 @@ package org.apache.sis.xml.bind.referencing;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlValue;
 import jakarta.xml.bind.annotation.XmlAttribute;
-import org.apache.sis.util.collection.Containers;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.internal.shared.Constants;
 import org.apache.sis.util.internal.shared.DefinitionURI;
 import org.apache.sis.metadata.internal.shared.NameMeaning;

Reply via email to