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

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 678c2091328264bbda465e821565cf54ffc78cde
Author: opencode <[email protected]>
AuthorDate: Thu Oct 8 14:08:04 2026 +0200

    Report a parse error for unterminated literals in SSI expressions
    
    An unterminated quoted string in an SSI conditional expression was
    silently accepted: the scan ran to the end of the expression and the
    remainder of the expression became the literal value, silently changing
    the semantics of the expression compared to what the document author
    intended.
    
    Signal this with a new TOKEN_ERROR token type when the closing quote is
    missing, and convert it into a ParseException in ExpressionParseTree so
    the failure takes the existing parse error path, stopping the processing
    of the document at the failing directive. Apply the same treatment to an
    unterminated regular expression for consistency, replacing the previous
    remainder-as-literal behavior.
---
 java/org/apache/catalina/ssi/ExpressionParseTree.java   |  3 +++
 java/org/apache/catalina/ssi/ExpressionTokenizer.java   | 15 ++++++++++++---
 java/org/apache/catalina/ssi/LocalStrings.properties    |  1 +
 .../apache/catalina/ssi/TestExpressionParseTree.java    | 17 +++++++++++++++++
 .../apache/catalina/ssi/TestExpressionTokenizer.java    | 14 +++++++++++---
 5 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/java/org/apache/catalina/ssi/ExpressionParseTree.java 
b/java/org/apache/catalina/ssi/ExpressionParseTree.java
index cf6dc1afa6..7ba512c1b5 100644
--- a/java/org/apache/catalina/ssi/ExpressionParseTree.java
+++ b/java/org/apache/catalina/ssi/ExpressionParseTree.java
@@ -241,6 +241,9 @@ public class ExpressionParseTree {
                     break;
                 case ExpressionTokenizer.TOKEN_END:
                     break;
+                case ExpressionTokenizer.TOKEN_ERROR:
+                    throw new 
ParseException(sm.getString("expressionParseTree.unterminatedLiteral"),
+                            et.getIndex());
             }
         }
         // Finish off the rest of the opps
diff --git a/java/org/apache/catalina/ssi/ExpressionTokenizer.java 
b/java/org/apache/catalina/ssi/ExpressionTokenizer.java
index 825b82c30a..981a85fe4b 100644
--- a/java/org/apache/catalina/ssi/ExpressionTokenizer.java
+++ b/java/org/apache/catalina/ssi/ExpressionTokenizer.java
@@ -48,6 +48,8 @@ public class ExpressionTokenizer {
     public static final int TOKEN_LT = 11;
     /** Token type indicating end of expression. */
     public static final int TOKEN_END = 12;
+    /** Token type indicating an unterminated string or regular expression. */
+    public static final int TOKEN_ERROR = 13;
     private final char[] expr;
     private String tokenVal = null;
     private int index;
@@ -176,6 +178,10 @@ public class ExpressionTokenizer {
                 }
                 escaped = false;
             }
+            if (index == length) {
+                // The closing quote is missing
+                return TOKEN_ERROR;
+            }
             end = index;
             index++; // Skip the end quote
         } else if (currentChar == '/') {
@@ -191,9 +197,12 @@ public class ExpressionTokenizer {
                 }
                 escaped = false;
             }
-            // If the regular expression was not terminated, the token is the
-            // remainder of the expression, otherwise it includes the closing 
slash
-            end = Math.min(++index, length);
+            if (index == length) {
+                // The closing slash is missing
+                return TOKEN_ERROR;
+            }
+            // The token includes the closing slash
+            end = ++index;
         } else {
             // End is the next whitespace character
             for (; index < length; index++) {
diff --git a/java/org/apache/catalina/ssi/LocalStrings.properties 
b/java/org/apache/catalina/ssi/LocalStrings.properties
index 5cebaec4d0..0fbbc0d9e4 100644
--- a/java/org/apache/catalina/ssi/LocalStrings.properties
+++ b/java/org/apache/catalina/ssi/LocalStrings.properties
@@ -20,6 +20,7 @@ expressionParseTree.extraNodes=Extra nodes created
 expressionParseTree.invalidExpression=Invalid expression [{0}]
 expressionParseTree.missingOperand=Missing operand
 expressionParseTree.noNodes=No nodes created
+expressionParseTree.unterminatedLiteral=Unterminated string or regular 
expression
 expressionParseTree.unusedOpCodes=Unused nodes exist
 
 ssiCommand.invalidAttribute=Invalid attribute [{0}]
diff --git a/test/org/apache/catalina/ssi/TestExpressionParseTree.java 
b/test/org/apache/catalina/ssi/TestExpressionParseTree.java
index 4865305a3c..0a3cbd5bf5 100644
--- a/test/org/apache/catalina/ssi/TestExpressionParseTree.java
+++ b/test/org/apache/catalina/ssi/TestExpressionParseTree.java
@@ -156,6 +156,23 @@ public class TestExpressionParseTree {
     }
 
 
+    @Test
+    public void testUnterminatedLiteral() throws Exception {
+        // Unterminated quoted strings and regular expressions must produce a
+        // parse error rather than silently changing the expression semantics
+        String[] expressions = { "a = \"x", "a = 'x", "a = /x", "\"", "/", "'x 
= y" };
+        for (String expression : expressions) {
+            SSIMediator mediator = new SSIMediator(new 
TesterSSIExternalResolver(), LAST_MODIFIED);
+            try {
+                new ExpressionParseTree(expression, mediator);
+                Assert.fail("Expected a parse error for [" + expression + "]");
+            } catch (ParseException pe) {
+                // Expected
+            }
+        }
+    }
+
+
     @Test
     public void testMissingOperand() throws Exception {
         // Operators missing an operand must produce a parse error rather than
diff --git a/test/org/apache/catalina/ssi/TestExpressionTokenizer.java 
b/test/org/apache/catalina/ssi/TestExpressionTokenizer.java
index e4f78cdb55..85bd512612 100644
--- a/test/org/apache/catalina/ssi/TestExpressionTokenizer.java
+++ b/test/org/apache/catalina/ssi/TestExpressionTokenizer.java
@@ -101,11 +101,19 @@ public class TestExpressionTokenizer {
         // Escaped slash inside a regular expression
         parameterSets.add(new Object[] { "/a\\/b/",
                 new int[] { ExpressionTokenizer.TOKEN_STRING }, new String[] { 
"/a\\/b/" } });
-        // Unterminated regular expression: the token is the remainder
+        // Unterminated regular expression yields an error token
         parameterSets.add(new Object[] { "a = /x",
                 new int[] { ExpressionTokenizer.TOKEN_STRING, 
ExpressionTokenizer.TOKEN_EQ,
-                        ExpressionTokenizer.TOKEN_STRING },
-                new String[] { "a", null, "/x" } });
+                        ExpressionTokenizer.TOKEN_ERROR },
+                new String[] { "a", null, null } });
+
+        // Unterminated quoted strings yield an error token
+        parameterSets.add(new Object[] { "a = \"x",
+                new int[] { ExpressionTokenizer.TOKEN_STRING, 
ExpressionTokenizer.TOKEN_EQ,
+                        ExpressionTokenizer.TOKEN_ERROR },
+                new String[] { "a", null, null } });
+        parameterSets.add(new Object[] { "'x",
+                new int[] { ExpressionTokenizer.TOKEN_ERROR }, new String[] { 
null } });
 
         // Operators mixed with strings
         parameterSets.add(new Object[] { "a && b",


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

Reply via email to