Repository: apache/jena
Issue: HttpLib throws NullPointerException if body is null.

### Version

4.7.0

### What happened?

This bug not only affect 4.7 but also 4.8 and the main branch.
Executing a certain query results in a httpStatusCode in the range of 400 to 499. This results in the call of the [exception()](https://github.com/apache/jena/blob/main/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java#L233) method which expects a response body in [line 241](https://github.com/apache/jena/blob/main/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java#L241). However the body is null which results in the _NullPointerException_ in the attached stacktrace.

### Relevant output and stacktrace

```shell
2023-04-13 16:27:33,289 : ERROR : KNIME-Worker-1-SPARQL Query 3:1 :  : Node : SPARQL Query : 3:1 : Execute failed: ("TripleStoreException"): null
org.knime.semanticweb.utility.TripleStoreException
      at org.knime.semanticweb.utility.SemanticWebUtility.executeQuery(SemanticWebUtility.java:405)
      at org.knime.semanticweb.nodes.query.SPARQLQueryNodeModel.run(SPARQLQueryNodeModel.java:170)
      at org.knime.semanticweb.nodes.query.SPARQLQueryNodeModel.execute(SPARQLQueryNodeModel.java:203)
      at org.knime.core.node.NodeModel.executeModel(NodeModel.java:549)
      at org.knime.core.node.Node.invokeFullyNodeModelExecute(Node.java:1267)
      at org.knime.core.node.Node.execute(Node.java:1041)
      at org.knime.core.node.workflow.NativeNodeContainer.performExecuteNode(NativeNodeContainer.java:595)
      at org.knime.core.node.exec.LocalNodeExecutionJob.mainExecute(LocalNodeExecutionJob.java:98)
      at org.knime.core.node.workflow.NodeExecutionJob.internalRun(NodeExecutionJob.java:201)
      at org.knime.core.node.workflow.NodeExecutionJob.run(NodeExecutionJob.java:117)
      at org.knime.core.util.ThreadUtils$RunnableWithContextImpl.runWithContext(ThreadUtils.java:367)
      at org.knime.core.util.ThreadUtils$RunnableWithContext.run(ThreadUtils.java:221)
      at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
      at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
      at org.knime.core.util.ThreadPool$MyFuture.run(ThreadPool.java:123)
      at org.knime.core.util.ThreadPool$Worker.run(ThreadPool.java:246)
Caused by: java.lang.NullPointerException
      at java.base/java.io.Reader.<init>(Unknown Source)
      at java.base/java.io.InputStreamReader.<init>(Unknown Source)
      at org.apache.jena.atlas.io.IO.asUTF8(IO.java:207)
      at org.apache.jena.atlas.io.IO.readWholeFileAsUTF8(IO.java:456)
      at org.apache.jena.http.HttpLib.exception(HttpLib.java:241)
      at org.apache.jena.http.HttpLib.handleHttpStatusCode(HttpLib.java:173)
      at org.apache.jena.sparql.exec.http.QueryExecHTTP.executeQuery(QueryExecHTTP.java:546)
      at org.apache.jena.sparql.exec.http.QueryExecHTTP.execRowSet(QueryExecHTTP.java:172)
      at org.apache.jena.sparql.exec.http.QueryExecHTTP.select(QueryExecHTTP.java:163)
      at org.apache.jena.sparql.exec.QueryExecutionAdapter.execSelect(QueryExecutionAdapter.java:117)
      at org.knime.semanticweb.utility.SemanticWebUtility.executeQuery(SemanticWebUtility.java:396)
      ... 15 more
```


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

Maybe

Changed files:
jena-arq/src/main/java/org/apache/jena/riot/other/G.java, jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java, jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java, jena-arq/src/main/java/org/apache/jena/sparql/SystemARQ.java, jena-cmds/src/main/java/arq/qexpr.java, jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValueCmp.java, jena-arq/src/main/java/org/apache/jena/http/HttpLib.java

diff --git a/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java b/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
index 96099669fb..5a16f13b41 100644
--- a/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
@@ -232,9 +232,9 @@ public class HttpLib {
      */
     static HttpException exception(HttpResponse<InputStream> response, int httpStatusCode) {
         URI uri = response.request().uri();
-        //long length = HttpLib.getContentLength(response);
-        // Not critical path code. Read body regardless.
         InputStream in = response.body();
+        if ( in == null )
+            return new HttpException(httpStatusCode, HttpSC.getMessage(httpStatusCode));
         try {
             String msg;
             try {
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/other/G.java b/jena-arq/src/main/java/org/apache/jena/riot/other/G.java
index bac5b2bc68..9becc261ad 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/other/G.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/other/G.java
@@ -941,7 +941,6 @@ public class G {
 //        Predicate<Triple> predicate = (t) -> sameTermMatch(o, t.getObject()) ;
 //        iter = iter.filterKeep(predicate) ;
 //    }
-
     }
 
     /**
@@ -963,11 +962,14 @@ public class G {
     public static boolean sameTermMatch(Node match, Node data) {
         if ( isNullOrAny(match) )
             return true;
+        if ( ! match.isLiteral() )
+            // Only literals have values
+            return match.equals(data);
 
         // Allow for case-insensitive language tag comparison.
         if ( ! Util.isLangString(data) || ! Util.isLangString(match) )
             // Any mix of node types except both strings with lang tags.
-            return match.equals(data) ;
+            return match.equals(data);
 
         // Both nodes with language tags : compare languages case insensitively.
         String lex1 = match.getLiteralLexicalForm();
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/SystemARQ.java b/jena-arq/src/main/java/org/apache/jena/sparql/SystemARQ.java
index 17054964cd..4c10668c7f 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/SystemARQ.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/SystemARQ.java
@@ -50,7 +50,7 @@ public class SystemARQ
     public static boolean ValueExtensions       = true ;
 
     /**
-     * Under strict {@literal F&O}, dateTimes and dates with no timezone have one magically applied.
+     * Under strict {@literal F&O}, dateTimes and dates that have no timezone have one magically applied.
      * This default timezone is implementation dependent and can lead to different answers
      * to queries depending on the timezone. Normally, ARQ uses XMLSchema dateTime comparisons,
      * which an yield "indeterminate", which in turn is an evaluation error.
@@ -58,7 +58,7 @@ public class SystemARQ
      */
     public static boolean StrictDateTimeFO      = false ;
 
-    /** Whether support for roman numerals (datatype http://rome.example.org/Numeral).
+    /** Whether support for Roman numerals (datatype http://rome.example.org/Numeral).
      *  Mainly a test of datatype extension.
      */
     public static boolean EnableRomanNumerals   = true ;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
index bfa08bf570..616f379e97 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
@@ -246,6 +246,8 @@ public class QueryExecHTTP implements QueryExec {
     }
 
     private String removeCharset(String contentType) {
+        if ( contentType == null )
+            return contentType;
         int idx = contentType.indexOf(';');
         if ( idx < 0 )
             return contentType;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValueCmp.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValueCmp.java
index da6748be36..9cf3993dc1 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValueCmp.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValueCmp.java
@@ -226,7 +226,7 @@ public class NodeValueCmp {
                 // Must be same URI
                 if ( nv1.getDatatypeURI().equals(nv2.getDatatypeURI()) )
                     // Indeterminate possible.
-                    return XSDFuncOp.compareDateTimeXSD(nv1, nv2);
+                    return XSDFuncOp.compareDateTime(nv1, nv2);
                 raise(new ExprNotComparableException("Can't compare (incompatible temporal value spaces) "+nv1+" and "+nv2)) ;
 
 //            case VSPACE_DURATION_DAYTIME:
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
index ce5c1d17a7..c1e5a7609d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
@@ -1141,14 +1141,13 @@ public class XSDFuncOp
         // "Comparison operators on xs:date, xs:gYearMonth and xs:gYear compare their
         // starting instants. These xs:dateTime values are calculated as described
         // below."
-        if ( SystemARQ.StrictDateTimeFO )
+       if ( SystemARQ.StrictDateTimeFO )
             return compareDateTimeFO(nv1, nv2) ;
-        return compareDateTimeXSD(nv1, nv2); //getXMLGregorianCalendarXSD(nv1), getXMLGregorianCalendarXSD(nv2)) ;
+        return compareDateTimeXSD(nv1, nv2);
     }
 
     /** Compare two date/times by XSD rules (dateTimes, one with and one without timezone can be indeterminate) */
     public static int compareDateTimeXSD(NodeValue nv1, NodeValue nv2) {
-
         XMLGregorianCalendar dt1 = getXMLGregorianCalendarXSD(nv1);
         XMLGregorianCalendar dt2 = getXMLGregorianCalendarXSD(nv2);
         int x = compareDateTime(dt1, dt2) ;
@@ -1669,7 +1668,6 @@ public class XSDFuncOp
         return XSDDuration.durationCompare(duration1, duration2);
     }
 
-
     public static final String implicitTimezoneStr = "Z" ;
     private static final NodeValue implicitTimezone_ = NodeValue.makeNode("PT0S", XSDDatatype.XSDdayTimeDuration);
 
diff --git a/jena-cmds/src/main/java/arq/qexpr.java b/jena-cmds/src/main/java/arq/qexpr.java
index fb46163c00..5b66138a23 100644
--- a/jena-cmds/src/main/java/arq/qexpr.java
+++ b/jena-cmds/src/main/java/arq/qexpr.java
@@ -64,9 +64,9 @@ public class qexpr
             if ( ex.getCause() != null )
                 ex.getCause().printStackTrace(System.err) ;
         }
-        
+
     }
-    
+
     public static void execAndReturn(String... argv)
     {
         try {
@@ -80,21 +80,21 @@ public class qexpr
                 ex.getCause().printStackTrace(System.err) ;
         }
     }
-        
+
     public static void main2(String... argv)
     {
-        
+
         CmdLineArgs cl = new CmdLineArgs(argv) ;
-        
+
         ArgDecl helpDecl = new ArgDecl(ArgDecl.NoValue, "h", "help") ;
         cl.add(helpDecl) ;
-        
+
         ArgDecl verboseDecl = new ArgDecl(ArgDecl.NoValue, "v", "verbose") ;
         cl.add(verboseDecl) ;
-        
+
         ArgDecl versionDecl = new ArgDecl(ArgDecl.NoValue, "ver", "version", "V") ;
         cl.add(versionDecl) ;
-        
+
         ArgDecl quietDecl = new ArgDecl(ArgDecl.NoValue, "q", "quiet") ;
         cl.add(quietDecl) ;
 
@@ -103,7 +103,7 @@ public class qexpr
 
         ArgDecl strictDecl =  new ArgDecl(ArgDecl.NoValue, "strict") ;
         cl.add(strictDecl) ;
-        
+
         ArgDecl printDecl =  new ArgDecl(ArgDecl.HasValue, "print") ;
         cl.add(printDecl) ;
 
@@ -121,23 +121,23 @@ public class qexpr
             usage() ;
             throw new TerminationException(0) ;
         }
-        
+
         if ( cl.contains(versionDecl) )
         {
             System.out.println("ARQ Version: "+ARQ.VERSION+" (Jena: "+Jena.VERSION+")") ;
             throw new TerminationException(0) ;
         }
- 
+
         // ==== General things
         boolean verbose = cl.contains(verboseDecl) ;
         boolean quiet = cl.contains(quietDecl) ;
 
         if ( cl.contains(strictDecl) )
             ARQ.setStrictMode() ;
-        
+
         boolean actionCopySubstitute = cl.contains(reduceDecl) ;
         boolean actionPrintPrefix = false ;
-        boolean actionPrintSPARQL = false ; 
+        boolean actionPrintSPARQL = false ;
         boolean actionPrint = cl.contains(printDecl) ;
 
         for ( String v : cl.getValues( printDecl ) )
@@ -158,12 +158,12 @@ public class qexpr
         }
 
         // ==== Do it
-        
+
         for ( int i = 0 ; i < cl.getNumPositional() ; i++ )
         {
             String exprStr = cl.getPositionalArg(i) ;
             exprStr = cl.indirect(exprStr) ;
-            
+
             try {
                 PrefixMapping pmap = PrefixMapping.Factory.create()  ;
                 pmap.setNsPrefixes(ARQConstants.getGlobalPrefixMap()) ;
@@ -174,7 +174,7 @@ public class qexpr
                 if ( actionPrint )
                 {
                     IndentedWriter iOut =  IndentedWriter.stdout;
-                    
+
                     if ( actionPrintSPARQL ) {
                         ExprUtils.fmtSPARQL(iOut, expr);
                         iOut.ensureStartOfLine();
@@ -200,7 +200,7 @@ public class qexpr
                     {
                         // Default action
                         ARQ.getContext().set(ARQConstants.sysCurrentTime, NodeFactoryExtra.nowAsDateTime()) ;
-                        FunctionEnv env = new ExecutionContext(ARQ.getContext(), null, null, null) ; 
+                        FunctionEnv env = new ExecutionContext(ARQ.getContext(), null, null, null) ;
                         NodeValue r = expr.eval(null, env) ;
                         //System.out.println(r.asQuotedString()) ;
                         Node n = r.asNode() ;
@@ -209,6 +209,8 @@ public class qexpr
                     }
                 } catch (ExprEvalException ex)
                 {
+                    ex.printStackTrace();
+
                     System.out.println("Exception: "+ex.getMessage()) ;
                     throw new TerminationException(2) ;
                 }
@@ -219,9 +221,9 @@ public class qexpr
             }
         }
     }
-    
+
     static void usage() { usage(System.out) ; }
-    
+
     static void usage(java.io.PrintStream out)
     {
         out.println("Usage: [--print=[prefix|expr]] expression") ;