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

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 8ae31ef796 Follow up to SSI fixes
8ae31ef796 is described below

commit 8ae31ef79624ffff3a61eecb823783d0cb1d2949
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Jun 29 14:16:26 2026 +0100

    Follow up to SSI fixes
    
    - Avoid AIOOB exception if no command is provided for exec
    - Log if the maximum substitutions limit is exceeded
    - Reduce chance of deadlock when reading stdout/stderr with exec
    - Handle entities from the supplementary plane - fix and test - Opus 4.8
---
 java/org/apache/catalina/ssi/LocalStrings.properties     |  2 ++
 java/org/apache/catalina/ssi/SSIExec.java                |  5 ++++-
 java/org/apache/catalina/ssi/SSIMediator.java            |  7 ++++---
 .../org/apache/catalina/ssi/TestExpressionParseTree.java | 16 ++++++++++++++++
 4 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/catalina/ssi/LocalStrings.properties 
b/java/org/apache/catalina/ssi/LocalStrings.properties
index 7fe8a96c31..b5ea18ab25 100644
--- a/java/org/apache/catalina/ssi/LocalStrings.properties
+++ b/java/org/apache/catalina/ssi/LocalStrings.properties
@@ -26,6 +26,7 @@ ssiCommand.invalidAttribute=Invalid attribute [{0}]
 ssiEcho.invalidEncoding=Invalid encoding [{0}]
 
 ssiExec.executeFailed=Cannot execute file [{0}]
+ssiExec.noCommand=No command was provided for SSI exec
 
 ssiFlastmod.noLastModified=Cannot get last modification date for file [{0}]
 
@@ -34,6 +35,7 @@ ssiFsize.noSize=Cannot get size for file [{0}]
 
 ssiInclude.includeFailed=Cannot include file [{0}]
 
+ssiMediator.maxSubstitutionsExceeded=The limit of [{0}] substitutions per 
expression has been exceeded
 ssiMediator.unknownEncoding=Unknown encoding [{0}]
 
 ssiServletExternalResolver.absoluteNonVirtualPath=Non virtual [{0}] path 
cannot be absolute
diff --git a/java/org/apache/catalina/ssi/SSIExec.java 
b/java/org/apache/catalina/ssi/SSIExec.java
index c6e7992781..ac71a0cb71 100644
--- a/java/org/apache/catalina/ssi/SSIExec.java
+++ b/java/org/apache/catalina/ssi/SSIExec.java
@@ -90,6 +90,9 @@ public class SSIExec implements SSICommand {
                     tokens.add(current.toString());
                 }
                 String[] cmdArray = tokens.toArray(new String[0]);
+                if (cmdArray.length == 0) {
+                    throw new IOException(sm.getString("ssiExec.noCommand"));
+                }
                 Process proc = rt.exec(cmdArray);
                 foundProgram = true;
                 char[] buf = new char[BUFFER_SIZE];
@@ -99,8 +102,8 @@ public class SSIExec implements SSICommand {
                                     new 
InputStreamReader(proc.getErrorStream()))) {
                         // We don't spawn a thread here, since this is costly. 
stderr would be usually written
                         // right away and the amount written would be small
-                        IOTools.flow(stdErrReader, writer, buf);
                         IOTools.flow(stdOutReader, writer, buf);
+                        IOTools.flow(stdErrReader, writer, buf);
                     }
                     proc.waitFor();
                 } finally {
diff --git a/java/org/apache/catalina/ssi/SSIMediator.java 
b/java/org/apache/catalina/ssi/SSIMediator.java
index 1ae39a254d..0f2140d693 100644
--- a/java/org/apache/catalina/ssi/SSIMediator.java
+++ b/java/org/apache/catalina/ssi/SSIMediator.java
@@ -346,10 +346,10 @@ public class SSIMediator {
                 boolean processed = false;
                 try {
                     int codePoint = Integer.parseInt(sb.substring(charStart + 
2, charEnd));
-                    if (codePoint >= 0 && codePoint <= 0xFFFF) {
-                        char c = (char) codePoint;
+                    if (Character.isValidCodePoint(codePoint)) {
+                        // Use toChars() so code points outside the BMP are 
inserted as a surrogate pair
                         sb.delete(charStart, charEnd + 1);
-                        sb.insert(charStart, c);
+                        sb.insert(charStart, Character.toChars(codePoint));
                         processed = true;
                     }
                 } catch (NumberFormatException e) {
@@ -440,6 +440,7 @@ public class SSIMediator {
             // count prevents infinite loops from circular variable references
             // (e.g. X="$Y" and Y="$X")
             if (++substitutionCount > maxSubstitutions) {
+                log(sm.getString("ssiMediator.maxSubstitutionsExceeded", 
Integer.toString(maxSubstitutions)));
                 break;
             }
             i = start;
diff --git a/test/org/apache/catalina/ssi/TestExpressionParseTree.java 
b/test/org/apache/catalina/ssi/TestExpressionParseTree.java
index 18ccebb9a2..14b63233da 100644
--- a/test/org/apache/catalina/ssi/TestExpressionParseTree.java
+++ b/test/org/apache/catalina/ssi/TestExpressionParseTree.java
@@ -208,6 +208,22 @@ public class TestExpressionParseTree {
         Assert.assertEquals("$Yresult", mediator.substituteVariables(input));
     }
 
+
+    @Test
+    public void testSubstituteVariablesNumericEntityBmp() throws Exception {
+        // A numeric character reference in the BMP is decoded
+        SSIMediator mediator = new SSIMediator(new 
TesterSSIExternalResolver(), LAST_MODIFIED);
+        Assert.assertEquals("A", mediator.substituteVariables("&#65;"));
+    }
+
+
+    @Test
+    public void testSubstituteVariablesNumericEntitySupplementary() throws 
Exception {
+        // A numeric character reference outside the BMP (U+1F600 = surrogate 
pair \uD83D\uDE00) must be decoded
+        SSIMediator mediator = new SSIMediator(new 
TesterSSIExternalResolver(), LAST_MODIFIED);
+        Assert.assertEquals("\uD83D\uDE00", 
mediator.substituteVariables("&#128512;"));
+    }
+
     /**
      * Minimal implementation that provides the bare essentials require for 
the unit tests.
      */


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to