File tomcat-9.0.36-CVE-2025-55752.patch of Package tomcat.43093

Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/valves/rewrite/RewriteValve.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/valves/rewrite/RewriteValve.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/valves/rewrite/RewriteValve.java
@@ -327,7 +327,7 @@ public class RewriteValve extends ValveB
             // As long as MB isn't a char sequence or affiliated, this has to be
             // converted to a string
             Charset uriCharset = request.getConnector().getURICharset();
-            String originalQueryStringEncoded = request.getQueryString();
+            String queryStringOriginalEncoded = request.getQueryString();
             MessageBytes urlMB =
                     context ? request.getRequestPathMB() : request.getDecodedRequestURIMB();
             urlMB.toChars();
@@ -424,11 +424,10 @@ public class RewriteValve extends ValveB
                     StringBuilder urlStringEncoded =
                             new StringBuilder(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
 
-                    if (!qsd && originalQueryStringEncoded != null
-                            && originalQueryStringEncoded.length() > 0) {
+                    if (!qsd && queryStringOriginalEncoded != null && !queryStringOriginalEncoded.isEmpty()) {
                         if (rewrittenQueryStringRewriteEncoded == null) {
                             urlStringEncoded.append('?');
-                            urlStringEncoded.append(originalQueryStringEncoded);
+                            urlStringEncoded.append(queryStringOriginalEncoded);
                         } else {
                             if (qsa) {
                                 // if qsa is specified append the query
@@ -436,7 +435,7 @@ public class RewriteValve extends ValveB
                                 urlStringEncoded.append(
                                         REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, uriCharset));                                
                                 urlStringEncoded.append('&');
-                                urlStringEncoded.append(originalQueryStringEncoded);
+                                urlStringEncoded.append(queryStringOriginalEncoded);
                             } else if (index == urlStringEncoded.length() - 1) {
                                 // if the ? is the last character delete it, its only purpose was to
                                 // prevent the rewrite module from appending the query string
@@ -553,9 +552,10 @@ public class RewriteValve extends ValveB
                     // Step 3. Complete the 2nd stage to encoding.
                     chunk.append(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
                     request.getCoyoteRequest().requestURI().toChars();
-                    // Decoded and normalized URI
-                    // Rewriting may have denormalized the URL
-                    urlStringRewriteEncoded = RequestUtil.normalize(urlStringRewriteEncoded);
+                    // Rewriting may have denormalized the URL and added encoded characters
+                    // Decode then normalize
+                    String urlStringRewriteDecoded = URLDecoder.decode(urlStringRewriteEncoded, uriCharset.name());
+                    urlStringRewriteDecoded = RequestUtil.normalize(urlStringRewriteDecoded);
                     request.getCoyoteRequest().decodedURI().setString(null);
                     chunk = request.getCoyoteRequest().decodedURI().getCharChunk();
                     chunk.recycle();
@@ -563,18 +563,23 @@ public class RewriteValve extends ValveB
                         // This is decoded and normalized
                         chunk.append(request.getServletContext().getContextPath());
                     }                    
-                    chunk.append(URLDecoder.decode(urlStringRewriteEncoded, uriCharset.name()));
+                    chunk.append(urlStringRewriteDecoded);
                     request.getCoyoteRequest().decodedURI().toChars();
-                    // Set the new Query if there is one
-                    if (queryStringRewriteEncoded != null) {
+                    // Set the new Query String
+                    if (queryStringRewriteEncoded == null) {
+                        // No new query string. Therefore the original is retained unless QSD is defined.
+                        if (qsd) {
+                            request.getCoyoteRequest().queryString().setString(null);
+                        }
+                    } else {
+                        // New query string. Therefore the original is dropped unless QSA is defined (and QSD is not).
                         request.getCoyoteRequest().queryString().setString(null);
                         chunk = request.getCoyoteRequest().queryString().getCharChunk();
                         chunk.recycle();
                         chunk.append(REWRITE_QUERY_ENCODER.encode(queryStringRewriteEncoded, uriCharset));
-                        if (qsa && originalQueryStringEncoded != null &&
-                                originalQueryStringEncoded.length() > 0) {
+                        if (qsa && queryStringOriginalEncoded != null && !queryStringOriginalEncoded.isEmpty()) {
                             chunk.append('&');
-                            chunk.append(originalQueryStringEncoded);
+                            chunk.append(queryStringOriginalEncoded);
                         }
                         if (!chunk.isNull()) {
                             request.getCoyoteRequest().queryString().toChars();
@@ -665,6 +670,10 @@ public class RewriteValve extends ValveB
                     while (flagsTokenizer.hasMoreElements()) {
                         parseRuleFlag(line, rule, flagsTokenizer.nextToken());
                     }
+                    // If QSD and QSA are present, QSD always takes precedence
+                    if (rule.isQsdiscard()) {
+                        rule.setQsappend(false);
+                    }
                 }
                 return rule;
             } else if (token.equals("RewriteMap")) {
Index: apache-tomcat-9.0.36-src/test/org/apache/catalina/startup/TomcatBaseTest.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/test/org/apache/catalina/startup/TomcatBaseTest.java
+++ apache-tomcat-9.0.36-src/test/org/apache/catalina/startup/TomcatBaseTest.java
@@ -138,6 +138,14 @@ public abstract class TomcatBaseTest ext
         return tomcat;
     }
 
+    public Context getProgrammaticRootContext() {
+        // No file system docBase required
+        Context ctx = tomcat.addContext("", null);
+        // Disable class path scanning - it slows the tests down by almost an order of magnitude
+        ((StandardJarScanner) ctx.getJarScanner()).setScanClassPath(false);
+        return ctx;
+    }
+
     /*
      * Sub-classes need to know port so they can connect
      */
@@ -551,7 +559,7 @@ public abstract class TomcatBaseTest ext
                         value.append(";");
                     }
                 }
-                out.println("PARAM/" + name + ": " + value);
+                out.println("PARAM:" + name + ": " + value);
             }
 
             out.println("SESSION-REQUESTED-ID: " +
Index: apache-tomcat-9.0.36-src/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
+++ apache-tomcat-9.0.36-src/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
@@ -23,6 +23,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -183,17 +185,112 @@ public class TestRewriteValve extends To
     }
 
     @Test
-    public void testQueryString() throws Exception {
+    public void testQueryStringTargetOnly() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?je=2", "/b/id=1", "/c/id=1", "je=2");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQSA() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?je=2 [QSA]", "/b/id=1", "/c/id=1", "je=2");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?je=2 [QSD]", "/b/id=1", "/c/id=1", "je=2");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQSAQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?je=2 [QSA,QSD]", "/b/id=1", "/c/id=1", "je=2");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQS() throws Exception {
         doTestRewrite("RewriteRule ^/b/(.*) /c?$1", "/b/id=1", "/c", "id=1");
     }
 
     @Test
+    public void testQueryStringTargetOnlyQSAQS() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c?$1 [QSA]", "/b/id=1", "/c", "id=1");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQSDQS() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c?$1 [QSD]", "/b/id=1", "/c", "id=1");
+    }
+
+    @Test
+    public void testQueryStringTargetOnlyQSAQSDQS() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c?$1 [QSA,QSD]", "/b/id=1", "/c", "id=1");
+    }
+
+    @Test
+    public void testQueryStringSourceOnly() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1", "/b/d?id=1", "/c/d", "id=1");
+    }
+
+    @Test
+    public void testQueryStringSourceOnlyQSA() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSA]", "/b/d?id=1", "/c/d", "id=1");
+    }
+
+    @Test
+    public void testQueryStringSourceOnlyQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSD]", "/b/d?id=1", "/c/d", null);
+    }
+
+    @Test
+    public void testQueryStringSourceOnlyQSAQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSA,QSD]", "/b/d?id=1", "/c/d", null);
+    }
+
+    @Test
+    public void testQueryStringSourceAndTarget() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?id=1", "/b/d?je=2", "/c/d", "id=1");
+    }
+
+    @Test
+    public void testQueryStringSourceAndTargetQSA() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?id=1 [QSA]", "/b/d?je=2", "/c/d", "id=1&je=2");
+    }
+
+    @Test
+    public void testQueryStringSourceAndTargetQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?id=1 [QSD]", "/b/d?je=2", "/c/d", "id=1");
+    }
+
+    @Test
+    public void testQueryStringSourceAndTargetQSAQSD() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?id=1 [QSA,QSD]", "/b/d?je=2", "/c/d", "id=1");
+    }
+
+    @Test
+    public void testQueryStringEncoded01() throws Exception {
+        doTestRewrite("RewriteCond %{QUERY_STRING} a=(.*)\nRewriteRule ^/b.*$ /%1 [QSD]", "/b?a=c", "/c", null);
+    }
+
+    @Test
+    public void testQueryStringEncoded02() throws Exception {
+        doTestRewrite("RewriteCond %{QUERY_STRING} a=(.*)\nRewriteRule ^/b.*$ /z/%1 [QSD]", "/b?a=%2e%2e%2fc%2faAbB", "/z/%2e%2e%2fc%2faAbB", null);
+    }
+
+    @Test
     public void testQueryStringRemove() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?", "/b/d?=1", "/c/d", null);
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?", "/b/d?id=1", "/c/d", null);
     }
 
     @Test
     public void testQueryStringRemove02() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSD]", "/b/d?id=1", "/c/d", null);
+    }
+
+    @Test
+    public void testQueryStringRemoveInvalid() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1?", "/b/d?=1", "/c/d", null);
+    }
+
+    @Test
+    public void testQueryStringRemoveInvalid02() throws Exception {
         doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [QSD]", "/b/d?=1", "/c/d", null);
     }
 
@@ -514,9 +611,8 @@ public class TestRewriteValve extends To
     @Test
     public void testFlagsNC() throws Exception {
         // https://bz.apache.org/bugzilla/show_bug.cgi?id=60116
-        doTestRewrite("RewriteCond %{QUERY_STRING} a=([a-z]*) [NC]\n"
-                + "RewriteRule .* - [E=X-Test:%1]",
-                    "/c?a=aAa", "/c", null, "aAa");
+        doTestRewrite("RewriteCond %{QUERY_STRING} a=([a-z]*) [NC]\n" + "RewriteRule .* - [E=X-Test:%1]", "/c?a=aAa",
+                "/c", "a=aAa", "aAa");
     }
 
 
@@ -672,12 +768,16 @@ public class TestRewriteValve extends To
             // were written into the request target
             Assert.assertEquals(400, rc);
         } else {
+            // If there is an expected URI, the request should be successful
+            Assert.assertEquals(200, rc);
             String body = res.toString();
             RequestDescriptor requestDesc = SnoopResult.parse(body);
             String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
             Assert.assertEquals(expectedURI, requestURI);
 
-            if (expectedQueryString != null) {
+            if (expectedQueryString == null) {
+                Assert.assertTrue(requestDesc.getParams().isEmpty());
+            } else {
                 String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING");
                 Assert.assertEquals(expectedQueryString, queryString);
             }
openSUSE Build Service is sponsored by