Repository: apache/jena (java)

Issue:
org.apache.jena.shared.PrefixMapping$IllegalPrefixException: 😀
### Version

4.9.0

### What happened?

Query fails after uploading file with prefix 😀

`DESCRIBE <https://mydomain.com/myResource> FROM <urn:x-arq:DefaultGraph>`

Fuseki only seems to have an issue when doing `DESCRIBE FROM` and `SELECT FROM`,  `DESCRIBE` and `SELECT` both work fine.

If I understand the trig standard should be a valid prefix.

https://www.w3.org/TR/trig/#grammar-production-PN_CHARS_BASE

### Relevant output and stacktrace

```shell
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149) [fuseki-server.jar:4.9.0]
	at java.lang.Thread.run(Unknown Source) [?:?]
00:17:51 INFO  Fuseki          :: [19130] 500 Server Error (0 ms)
00:17:51 INFO  Fuseki          :: [19129] 500 Server Error (0 ms)
00:21:30 INFO  Fuseki          :: [19131] POST http://fuseki.mydomain.com/ds/sparql
00:21:30 INFO  Fuseki          :: [19131] Query = DESCRIBE <https://mydomain.com/myResource> FROM <urn:x-arq:DefaultGraph>
00:21:30 WARN  Fuseki          :: [19131] RC = 500 : 😀
org.apache.jena.shared.PrefixMapping$IllegalPrefixException: 😀
	at org.apache.jena.shared.impl.PrefixMappingImpl.checkLegal(PrefixMappingImpl.java:181) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.shared.impl.PrefixMappingImpl.setNsPrefix(PrefixMappingImpl.java:80) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.shared.impl.PrefixMappingImpl.setNsPrefixes(PrefixMappingImpl.java:171) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.shared.impl.PrefixMappingImpl.setNsPrefixes(PrefixMappingImpl.java:138) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.graph.GraphUnionRead.lambda$createPrefixMapping$0(GraphUnionRead.java:85) ~[fuseki-server.jar:4.9.0]
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) ~[?:?]
	at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source) ~[?:?]
	at java.util.stream.ReferencePipeline$2$1.accept(Unknown Source) ~[?:?]
	at java.util.HashMap$KeySpliterator.forEachRemaining(Unknown Source) ~[?:?]
	at java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:?]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[?:?]
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source) ~[?:?]
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source) ~[?:?]
	at java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:?]
	at java.util.stream.ReferencePipeline.forEach(Unknown Source) ~[?:?]
	at org.apache.jena.sparql.graph.GraphUnionRead.forEachGraph(GraphUnionRead.java:131) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.graph.GraphUnionRead.createPrefixMapping(GraphUnionRead.java:83) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.graph.impl.GraphBase.getPrefixMapping(GraphBase.java:154) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.core.DatasetGraphMapLink.setDefaultGraph(DatasetGraphMapLink.java:183) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.core.DatasetGraphMapLink.<init>(DatasetGraphMapLink.java:97) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.core.DatasetGraphMapLink.<init>(DatasetGraphMapLink.java:91) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.core.DynamicDatasets.dynamicDataset(DynamicDatasets.java:83) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.sparql.core.DynamicDatasets.dynamicDataset(DynamicDatasets.java:65) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQL_QueryDataset.decideDatasetDynamic(SPARQL_QueryDataset.java:56) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQL_QueryDataset.decideDataset(SPARQL_QueryDataset.java:39) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQLQueryProcessor.execute(SPARQLQueryProcessor.java:272) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQLQueryProcessor.executeWithParameter(SPARQLQueryProcessor.java:224) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQLQueryProcessor.execute(SPARQLQueryProcessor.java:209) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.ActionService.executeLifecycle(ActionService.java:58) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.SPARQLQueryProcessor.execPost(SPARQLQueryProcessor.java:84) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.ActionProcessor.process(ActionProcessor.java:34) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.ActionBase.process(ActionBase.java:54) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.ActionExecLib.execActionSub(ActionExecLib.java:127) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.ActionExecLib.execAction(ActionExecLib.java:101) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.server.Dispatcher.dispatchAction(Dispatcher.java:231) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.server.Dispatcher.process(Dispatcher.java:221) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.server.Dispatcher.dispatch(Dispatcher.java:148) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.FusekiFilter.doFilter(FusekiFilter.java:49) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370) ~[fuseki-server.jar:4.9.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.CrossOriginFilter.handle(CrossOriginFilter.java:334) ~[fuseki-server.jar:4.9.0]
	at org.apache.jena.fuseki.servlets.CrossOriginFilter.doFilter(CrossOriginFilter.java:288) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:210) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1570) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1384) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1543) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1306) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:822) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.Server.handle(Server.java:563) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:505) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:762) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:497) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140) ~[fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194) [fuseki-server.jar:4.9.0]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149) [fuseki-server.jar:4.9.0]
	at java.lang.Thread.run(Unknown Source) [?:?]
```


### Are you interested in making a pull request?

None

Pull request description:
GH-2383: Protect GraphUnionRead against unacceptable prefix mappings
GitHub issue resolved #2383

Pull request Description:
Protect `GraphUnionRead` against unacceptable prefix mappings.


----

 - [x] Tests are included.
 - [x] Commits have been squashed to remove intermediate development commit messages.
 - [x] Key commit messages start with the issue number (GH-xxxx)

By submitting this pull request, I acknowledge that I am making a contribution to the Apache Software Foundation under the terms and conditions of the [Contributor's Agreement](https://www.apache.org/licenses/contributor-agreements.html).

----

See the [Apache Jena "Contributing" guide](https://github.com/apache/jena/blob/main/CONTRIBUTING.md).


Patch:
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFLib.java b/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFLib.java
index 2b3df62684..20b9294b19 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFLib.java
@@ -215,7 +215,7 @@ public class StreamRDFLib
 
         @Override
         public void prefix(String prefix, String uri) {
-            try { // Some graphs applies XML rules to prefixes.
+            try { // Some graphs apply XML rules to prefixes.
                 graph.getPrefixMapping().setNsPrefix(prefix, uri);
             } catch (JenaException ex) {}
         }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/graph/GraphUnionRead.java b/jena-arq/src/main/java/org/apache/jena/sparql/graph/GraphUnionRead.java
index 60172b9b52..96df48e2dd 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/graph/GraphUnionRead.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/graph/GraphUnionRead.java
@@ -20,6 +20,8 @@ package org.apache.jena.sparql.graph;
 
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.Consumer;
 
 import org.apache.jena.atlas.iterator.Iter;
@@ -81,8 +83,13 @@ public class GraphUnionRead extends GraphBase {
     protected PrefixMapping createPrefixMapping() {
         PrefixMapping pmap = new PrefixMappingImpl();
         forEachGraph((g) -> {
-            PrefixMapping pmapNamedGraph = g.getPrefixMapping();
-            pmap.setNsPrefixes(pmapNamedGraph);
+            Map<String, String> map = g.getPrefixMapping().getNsPrefixMap();
+            for (Entry<String, String> e: map.entrySet()) {
+                try {
+                    // PrefixMapping protects against RDF/XML illegal prefixes.
+                    pmap.setNsPrefix( e.getKey(), e.getValue() );
+                } catch (PrefixMapping.IllegalPrefixException ex) { /* ignore unacceptable prefixes */ }
+            }
         });
         return pmap;
     }


Test patch:
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/graph/TestGraphUnionRead.java b/jena-arq/src/test/java/org/apache/jena/sparql/graph/TestGraphUnionRead.java
index fca53928cb..a447c46040 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/graph/TestGraphUnionRead.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/graph/TestGraphUnionRead.java
@@ -19,6 +19,9 @@
 package org.apache.jena.sparql.graph;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import java.util.Arrays;
 import java.util.List;
@@ -29,6 +32,7 @@ import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
 import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
 import org.apache.jena.sparql.sse.Item;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.sparql.sse.builders.BuilderGraph;
@@ -46,7 +50,7 @@ public class TestGraphUnionRead
       "   (triple <http://example/s> <http://example/p> 'g1')",
       "   (triple <http://example/s> <http://example/p> <http://example/o>)",
       " )",
-      " (graph <http://example/g2>", 
+      " (graph <http://example/g2>",
       "   (triple <http://example/s> <http://example/p> 'g2')",
       "   (triple <http://example/s> <http://example/p> <http://example/o>)",
       " )",
@@ -63,7 +67,7 @@ public class TestGraphUnionRead
     private static Node gn2 = SSE.parseNode("<http://example/g2>");
     private static Node gn3 = SSE.parseNode("<http://example/g3>");
     private static Node gn9 = SSE.parseNode("<http://example/g9>");
-    
+
     @Test
     public void gr_union_01() {
         List<Node> gnodes = list(gn1, gn2);
@@ -125,6 +129,44 @@ public class TestGraphUnionRead
         assertEquals(1, x2);
     }
 
+    @Test
+    public void gr_union_prefixes() {
+        Graph graph = setupPrefixGraph("ex", "http://example/");
+
+        DatasetGraph dsg = DatasetGraphFactory.createGeneral();
+        dsg.addGraph(gn1, graph);
+
+        GraphUnionRead gUnionRead = new GraphUnionRead(dsg, List.of(gn1));
+        assertNotNull(gUnionRead.getPrefixMapping().getNsPrefixURI("ex"));
+    }
+
+    @Test
+    public void gr_union_prefixes_bad_PrefixMapping() {
+        // Turtle-valid, RDF/XML invalid prefix.
+        Graph graph = setupPrefixGraph("😀", "http://example/");
+
+        // Test : Must be createGeneral
+        DatasetGraph dsg = DatasetGraphFactory.createGeneral();
+        dsg.addGraph(gn1, graph);
+
+        // Builds a PrefixMapping when created.
+        GraphUnionRead gUnionRead = new GraphUnionRead(dsg, List.of(gn1));
+        // .. but skips the unacceptable prefix.
+        assertTrue(gUnionRead.getPrefixMapping().hasNoMappings());
+    }
+
+    private static Graph setupPrefixGraph(String prefix, String uriStr) {
+        // Must be createTxnMem
+        DatasetGraph dsg = DatasetGraphFactory.createTxnMem();
+        dsg.prefixes().add(prefix, uriStr);
+        Graph graph = dsg.getDefaultGraph();
+        // Check it has the prefix.
+        assertFalse(graph.getPrefixMapping().hasNoMappings());
+        assertTrue(graph.getPrefixMapping().getNsPrefixURI(prefix) != null);
+        assertTrue(graph.getPrefixMapping().getNsPrefixMap().containsKey(prefix));
+        return graph;
+    }
+
     static List<Node> list(Node...x) {
         return Arrays.asList(x);
     }