diff --git a/CVE-2025-31650-1.patch b/CVE-2025-31650-1.patch
new file mode 100644
index 0000000..d7934cd
--- /dev/null
+++ b/CVE-2025-31650-1.patch
@@ -0,0 +1,110 @@
+From 40ae788c2e64d018b4e58cd4210bb96434d0100d Mon Sep 17 00:00:00 2001
+From: Mark Thomas
+Date: Tue, 18 Mar 2025 12:24:09 +0000
+Subject: [PATCH] Fix BZ 69614 - invalid priority field values should be
+ ignored
+
+Origin: https://github.com/apache/tomcat/commit/40ae788c2e64d018b4e58cd4210bb96434d0100d
+
+---
+ java/org/apache/coyote/http2/Http2Parser.java | 23 +++++++++++------
+ .../coyote/http2/LocalStrings.properties | 1 +
+ test/org/apache/coyote/http2/TestRfc9218.java | 25 +++++++++++++++++++
+ 4 files changed, 46 insertions(+), 7 deletions(-)
+
+diff --git a/java/org/apache/coyote/http2/Http2Parser.java b/java/org/apache/coyote/http2/Http2Parser.java
+index db7c2fd496a2..90c1142616b7 100644
+--- a/java/org/apache/coyote/http2/Http2Parser.java
++++ b/java/org/apache/coyote/http2/Http2Parser.java
+@@ -477,15 +477,24 @@ protected void readPriorityUpdateFrame(int payloadSize, ByteBuffer buffer) throw
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(payload, 4, payloadSize - 4);
+ Reader r = new BufferedReader(new InputStreamReader(bais, StandardCharsets.US_ASCII));
+- Priority p = Priority.parsePriority(r);
+
+- if (log.isTraceEnabled()) {
+- log.trace(sm.getString("http2Parser.processFramePriorityUpdate.debug", connectionId,
+- Integer.toString(prioritizedStreamID), Integer.toString(p.getUrgency()),
+- Boolean.valueOf(p.getIncremental())));
+- }
++ try {
++ Priority p = Priority.parsePriority(r);
+
+- output.priorityUpdate(prioritizedStreamID, p);
++ if (log.isTraceEnabled()) {
++ log.trace(sm.getString("http2Parser.processFramePriorityUpdate.debug", connectionId,
++ Integer.toString(prioritizedStreamID), Integer.toString(p.getUrgency()),
++ Boolean.valueOf(p.getIncremental())));
++ }
++
++ output.priorityUpdate(prioritizedStreamID, p);
++ } catch (IllegalArgumentException iae) {
++ // Priority frames with invalid priority field values should be ignored
++ if (log.isTraceEnabled()) {
++ log.trace(sm.getString("http2Parser.processFramePriorityUpdate.invalid", connectionId,
++ Integer.toString(prioritizedStreamID)), iae);
++ }
++ }
+ }
+
+
+diff --git a/java/org/apache/coyote/http2/LocalStrings.properties b/java/org/apache/coyote/http2/LocalStrings.properties
+index 6ab82e8bdb9b..114f546017d4 100644
+--- a/java/org/apache/coyote/http2/LocalStrings.properties
++++ b/java/org/apache/coyote/http2/LocalStrings.properties
+@@ -77,6 +77,7 @@ http2Parser.processFrameHeaders.decodingDataLeft=Data left over after HPACK deco
+ http2Parser.processFrameHeaders.decodingFailed=There was an error during the HPACK decoding of HTTP headers
+ http2Parser.processFrameHeaders.payload=Connection [{0}], Stream [{1}], Processing headers payload of size [{2}]
+ http2Parser.processFramePriorityUpdate.debug=Connection [{0}], Stream [{1}], Urgency [{2}], Incremental [{3}]
++http2Parser.processFramePriorityUpdate.invalid=Connection [{0}], Stream [{1}], Priority Update frame with invalid priority field value
+ http2Parser.processFramePriorityUpdate.streamZero=Connection [{0}], Priority update frame received to prioritize stream zero
+ http2Parser.processFramePushPromise=Connection [{0}], Stream [{1}], Push promise frames should not be sent by the client
+ http2Parser.processFrameSettings.ackWithNonZeroPayload=Settings frame received with the ACK flag set and payload present
+diff --git a/test/org/apache/coyote/http2/TestRfc9218.java b/test/org/apache/coyote/http2/TestRfc9218.java
+index eb9256d2a140..1a6081f88c99 100644
+--- a/test/org/apache/coyote/http2/TestRfc9218.java
++++ b/test/org/apache/coyote/http2/TestRfc9218.java
+@@ -17,6 +17,7 @@
+ package org.apache.coyote.http2;
+
+ import java.io.IOException;
++import java.nio.charset.StandardCharsets;
+
+ import org.junit.Assert;
+ import org.junit.Test;
+@@ -146,6 +147,9 @@ public void testPriority() throws Exception {
+ // 19 - 7021 body left
+ // 21 - 6143 body left
+
++ // BZ 69614 - invalid priority update frames should be ignored
++ sendInvalidPriorityUpdate(17);
++
+ // Re-order the priorities
+ sendPriorityUpdate(17, 2, true);
+
+@@ -191,4 +195,25 @@ public void testPriority() throws Exception {
+ ioe.printStackTrace();
+ }
+ }
++
++
++ private void sendInvalidPriorityUpdate(int streamId) throws IOException {
++ byte[] payload = "u=1:i".getBytes(StandardCharsets.US_ASCII);
++
++ byte[] priorityUpdateFrame = new byte[13 + payload.length];
++
++ // length
++ ByteUtil.setThreeBytes(priorityUpdateFrame, 0, 4 + payload.length);
++ // type
++ priorityUpdateFrame[3] = FrameType.PRIORITY_UPDATE.getIdByte();
++ // Stream ID
++ ByteUtil.set31Bits(priorityUpdateFrame, 5, 0);
++
++ // Payload
++ ByteUtil.set31Bits(priorityUpdateFrame, 9, streamId);
++ System.arraycopy(payload, 0, priorityUpdateFrame, 13, payload.length);
++
++ os.write(priorityUpdateFrame);
++ os.flush();
++ }
+ }
diff --git a/CVE-2025-31650-2.patch b/CVE-2025-31650-2.patch
new file mode 100644
index 0000000..467aa51
--- /dev/null
+++ b/CVE-2025-31650-2.patch
@@ -0,0 +1,28 @@
+From b98e74f517b36929f4208506e5adad22cb767baa Mon Sep 17 00:00:00 2001
+From: Mark Thomas
+Date: Wed, 19 Mar 2025 03:21:50 +0000
+Subject: [PATCH] Additional fix for BZ 69614
+
+Origin: https://github.com/apache/tomcat/commit/b98e74f517b36929f4208506e5adad22cb767baa
+
+---
+ java/org/apache/coyote/http2/Stream.java | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/java/org/apache/coyote/http2/Stream.java b/java/org/apache/coyote/http2/Stream.java
+index e8f0d9481c03..ecda8caec811 100644
+--- a/java/org/apache/coyote/http2/Stream.java
++++ b/java/org/apache/coyote/http2/Stream.java
+@@ -456,6 +456,12 @@ public final void emitHeader(String name, String value) throws HpackException {
+ setIncremental(p.getIncremental());
+ } catch (IOException ioe) {
+ // Not possible with StringReader
++ } catch (IllegalArgumentException iae) {
++ // Invalid priority header field values should be ignored
++ if (log.isTraceEnabled()) {
++ log.trace(sm.getString("http2Parser.processFramePriorityUpdate.invalid", getConnectionId(),
++ getIdAsString()), iae);
++ }
+ }
+ break;
+ }
diff --git a/CVE-2025-31650-3.patch b/CVE-2025-31650-3.patch
new file mode 100644
index 0000000..75a21b6
--- /dev/null
+++ b/CVE-2025-31650-3.patch
@@ -0,0 +1,51 @@
+From b7674782679e1514a0d154166b1d04d38aaac4a9 Mon Sep 17 00:00:00 2001
+From: Mark Thomas
+Date: Wed, 19 Mar 2025 03:47:11 +0000
+Subject: [PATCH] Improve handling of unexpected errors during HTTP/2
+ processing
+
+Origin: https://github.com/apache/tomcat/commit/b7674782679e1514a0d154166b1d04d38aaac4a9
+
+---
+ java/org/apache/coyote/http2/Http2UpgradeHandler.java | 8 ++++++++
+ java/org/apache/coyote/http2/LocalStrings.properties | 1 +
+ 3 files changed, 12 insertions(+)
+
+diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+index 54e119b3f36b..38a81309a6e0 100644
+--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
++++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+@@ -47,6 +47,7 @@
+ import org.apache.coyote.http2.Http2Parser.Output;
+ import org.apache.juli.logging.Log;
+ import org.apache.juli.logging.LogFactory;
++import org.apache.tomcat.util.ExceptionUtils;
+ import org.apache.tomcat.util.http.MimeHeaders;
+ import org.apache.tomcat.util.http.parser.Priority;
+ import org.apache.tomcat.util.log.UserDataHelper;
+@@ -439,6 +440,13 @@ public SocketState upgradeDispatch(SocketEvent status) {
+ log.debug(sm.getString("upgradeHandler.ioerror", connectionId), ioe);
+ }
+ close();
++ } catch (Throwable t) {
++ ExceptionUtils.handleThrowable(t);
++ if (log.isDebugEnabled()) {
++ log.debug(sm.getString("upgradeHandler.throwable", connectionId), t);
++ }
++ // Unexpected errors close the connection.
++ close();
+ }
+
+ if (log.isTraceEnabled()) {
+diff --git a/java/org/apache/coyote/http2/LocalStrings.properties b/java/org/apache/coyote/http2/LocalStrings.properties
+index 114f546017d4..61cb8497bf4b 100644
+--- a/java/org/apache/coyote/http2/LocalStrings.properties
++++ b/java/org/apache/coyote/http2/LocalStrings.properties
+@@ -160,6 +160,7 @@ upgradeHandler.stream.error=Connection [{0}], Stream [{1}] Closed due to error
+ upgradeHandler.stream.even=A new remote stream ID of [{0}] was requested but all remote streams must use odd identifiers
+ upgradeHandler.stream.notWritable=Connection [{0}], Stream [{1}], This stream is in state [{2}] and is not writable
+ upgradeHandler.stream.old=A new remote stream ID of [{0}] was requested but the most recent stream was [{1}]
++upgradeHandler.throwable=Connection [{0}]
+ upgradeHandler.tooManyRemoteStreams=The client attempted to use more than [{0}] active streams
+ upgradeHandler.tooMuchOverhead=Connection [{0}], Too much overhead so the connection will be closed
+ upgradeHandler.unexpectedAck=Connection [{0}], Stream [{1}], A settings acknowledgement was received when not expected
diff --git a/CVE-2025-31651-1.patch b/CVE-2025-31651-1.patch
new file mode 100644
index 0000000..820c905
--- /dev/null
+++ b/CVE-2025-31651-1.patch
@@ -0,0 +1,562 @@
+From ee3ab548e92345eca0cbd1f01649eb36c6f29454 Mon Sep 17 00:00:00 2001
+From: Mark Thomas
+Date: Thu, 13 Mar 2025 15:06:26 +0000
+Subject: [PATCH] Better handling of URLs with literal ';' and '?'
+
+Origin: https://github.com/apache/tomcat/commit/ee3ab548e92345eca0cbd1f01649eb36c6f29454
+
+---
+ .../catalina/connector/CoyoteAdapter.java | 18 +--
+ .../catalina/valves/rewrite/RewriteValve.java | 137 ++++++++++++++----
+ .../valves/rewrite/TestRewriteValve.java | 119 ++++++++++++---
+ webapps/docs/rewrite.xml | 22 +++
+ 4 files changed, 241 insertions(+), 55 deletions(-)
+
+diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
+index 3c09f24..00086bd 100644
+--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
++++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
+@@ -653,17 +653,17 @@ public class CoyoteAdapter implements Adapter {
+ } else {
+ /*
+ * The URI is chars or String, and has been sent using an in-memory protocol handler. The following
+- * assumptions are made: - req.requestURI() has been set to the 'original' non-decoded, non-normalized
+- * URI - req.decodedURI() has been set to the decoded, normalized form of req.requestURI()
++ * assumptions are made:
++ *
++ * - req.requestURI() has been set to the 'original' non-decoded, non-normalized URI that includes path
++ * parameters (if any)
++ *
++ * - req.decodedURI() has been set to the decoded, normalized form of req.requestURI() with any path
++ * parameters removed
++ *
++ * - 'suspicious' URI filtering, if required, has already been performed
+ */
+ decodedURI.toChars();
+- // Remove all path parameters; any needed path parameter should be set
+- // using the request object rather than passing it in the URL
+- CharChunk uriCC = decodedURI.getCharChunk();
+- int semicolon = uriCC.indexOf(';');
+- if (semicolon > 0) {
+- decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), semicolon);
+- }
+ }
+ }
+
+diff --git a/java/org/apache/catalina/valves/rewrite/RewriteValve.java b/java/org/apache/catalina/valves/rewrite/RewriteValve.java
+index 6eb01e5..d1071e9 100644
+--- a/java/org/apache/catalina/valves/rewrite/RewriteValve.java
++++ b/java/org/apache/catalina/valves/rewrite/RewriteValve.java
+@@ -21,6 +21,7 @@ import java.io.IOException;
+ import java.io.InputStream;
+ import java.io.InputStreamReader;
+ import java.io.StringReader;
++import java.net.URLDecoder;
+ import java.nio.charset.Charset;
+ import java.nio.charset.StandardCharsets;
+ import java.util.ArrayList;
+@@ -64,6 +65,24 @@ import org.apache.tomcat.util.http.RequestUtil;
+ */
+ public class RewriteValve extends ValveBase {
+
++ private static final URLEncoder REWRITE_DEFAULT_ENCODER;
++ private static final URLEncoder REWRITE_QUERY_ENCODER;
++
++ static {
++ /*
++ * See the detailed explanation of encoding/decoding during URL re-writing in the invoke() method.
++ *
++ * These encoders perform the second stage of encoding, after re-writing has completed. These rewrite specific
++ * encoders treat '%' as a safe character so that URLs and query strings already processed by encodeForRewrite()
++ * do not end up with double encoding of '%' characters.
++ */
++ REWRITE_DEFAULT_ENCODER = (URLEncoder) URLEncoder.DEFAULT.clone();
++ REWRITE_DEFAULT_ENCODER.addSafeCharacter('%');
++
++ REWRITE_QUERY_ENCODER = (URLEncoder) URLEncoder.QUERY.clone();
++ REWRITE_QUERY_ENCODER.addSafeCharacter('%');
++ }
++
+ /**
+ * The rewrite rules that the valve will use.
+ */
+@@ -297,22 +316,51 @@ public class RewriteValve extends ValveBase {
+
+ invoked.set(Boolean.TRUE);
+
+- // As long as MB isn't a char sequence or affiliated, this has to be
+- // converted to a string
++ // 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();
+ MessageBytes urlMB = context ? request.getRequestPathMB() : request.getDecodedRequestURIMB();
+ urlMB.toChars();
+ CharSequence urlDecoded = urlMB.getCharChunk();
++
++ /*
++ * The URL presented to the rewrite valve is the URL that is used for request mapping. That URL has been
++ * processed to: remove path parameters; remove the query string; decode; and normalize the URL. It may
++ * contain literal '%', '?' and/or ';' characters at this point.
++ *
++ * The re-write rules need to be able to process URLs with literal '?' characters and add query strings
++ * without the two becoming confused. The re-write rules also need to be able to insert literal '%'
++ * characters without them being confused with %nn encoding.
++ *
++ * The re-write rules cannot insert path parameters.
++ *
++ * To meet these requirement, the URL is processed as follows.
++ *
++ * Step 1. The URL is partially re-encoded by encodeForRewrite(). This method encodes any literal '%', ';'
++ * and/or '?' characters in the URL using the standard %nn form.
++ *
++ * Step 2. The re-write processing runs with the provided re-write rules against the partially encoded URL.
++ * If a re-write rule needs to insert a literal '%', ';' or '?', it must do so in %nn encoded form.
++ *
++ * Step 3. The URL (and query string if present) is re-encoded using the re-write specific encoders
++ * (REWRITE_DEFAULT_ENCODER and REWRITE_QUERY_ENCODER) that behave the same was as the standard encoders
++ * apart from '%' being treated as a safe character. This prevents double encoding of any '%' characters
++ * present in the URL from steps 1 or 2.
++ */
++
++ // Step 1. Encode URL for processing by the re-write rules.
++ CharSequence urlRewriteEncoded = encodeForRewrite(urlDecoded);
+ CharSequence host = request.getServerName();
+ boolean rewritten = false;
+ boolean done = false;
+ boolean qsa = false;
+ boolean qsd = false;
+ boolean valveSkip = false;
++
++ // Step 2. Process the URL using the re-write rules.
+ for (int i = 0; i < rules.length; i++) {
+ RewriteRule rule = rules[i];
+- CharSequence test = (rule.isHost()) ? host : urlDecoded;
++ CharSequence test = (rule.isHost()) ? host : urlRewriteEncoded;
+ CharSequence newtest = rule.evaluate(test, resolver);
+ if (newtest != null && !test.toString().equals(newtest.toString())) {
+ if (containerLog.isTraceEnabled()) {
+@@ -322,7 +370,7 @@ public class RewriteValve extends ValveBase {
+ if (rule.isHost()) {
+ host = newtest;
+ } else {
+- urlDecoded = newtest;
++ urlRewriteEncoded = newtest;
+ }
+ rewritten = true;
+ }
+@@ -359,28 +407,29 @@ public class RewriteValve extends ValveBase {
+ if (rule.isRedirect() && newtest != null) {
+ // Append the query string to the url if there is one and it
+ // hasn't been rewritten
+- String urlStringDecoded = urlDecoded.toString();
+- int index = urlStringDecoded.indexOf('?');
+- String rewrittenQueryStringDecoded;
++ String urlStringRewriteEncoded = urlRewriteEncoded.toString();
++ int index = urlStringRewriteEncoded.indexOf('?');
++ String rewrittenQueryStringRewriteEncoded;
+ if (index == -1) {
+- rewrittenQueryStringDecoded = null;
++ rewrittenQueryStringRewriteEncoded = null;
+ } else {
+- rewrittenQueryStringDecoded = urlStringDecoded.substring(index + 1);
+- urlStringDecoded = urlStringDecoded.substring(0, index);
++ rewrittenQueryStringRewriteEncoded = urlStringRewriteEncoded.substring(index + 1);
++ urlStringRewriteEncoded = urlStringRewriteEncoded.substring(0, index);
+ }
+
+- StringBuilder urlStringEncoded =
+- new StringBuilder(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
++ // Step 3. Complete the 2nd stage to encoding.
++ StringBuilder urlStringEncoded =
++ new StringBuilder(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
+ if (!qsd && originalQueryStringEncoded != null && originalQueryStringEncoded.length() > 0) {
+- if (rewrittenQueryStringDecoded == null) {
++ if (rewrittenQueryStringRewriteEncoded == null) {
+ urlStringEncoded.append('?');
+ urlStringEncoded.append(originalQueryStringEncoded);
+ } else {
+ if (qsa) {
+ // if qsa is specified append the query
+ urlStringEncoded.append('?');
+- urlStringEncoded
+- .append(URLEncoder.QUERY.encode(rewrittenQueryStringDecoded, uriCharset));
++ urlStringEncoded.append(
++ REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, uriCharset));
+ urlStringEncoded.append('&');
+ urlStringEncoded.append(originalQueryStringEncoded);
+ } else if (index == urlStringEncoded.length() - 1) {
+@@ -389,13 +438,14 @@ public class RewriteValve extends ValveBase {
+ urlStringEncoded.deleteCharAt(index);
+ } else {
+ urlStringEncoded.append('?');
+- urlStringEncoded
+- .append(URLEncoder.QUERY.encode(rewrittenQueryStringDecoded, uriCharset));
++ urlStringEncoded.append(
++ REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, uriCharset));
+ }
+ }
+- } else if (rewrittenQueryStringDecoded != null) {
++ } else if (rewrittenQueryStringRewriteEncoded != null) {
+ urlStringEncoded.append('?');
+- urlStringEncoded.append(URLEncoder.QUERY.encode(rewrittenQueryStringDecoded, uriCharset));
++ urlStringEncoded
++ .append(REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, uriCharset));
+ }
+
+ // Insert the context if
+@@ -470,12 +520,12 @@ public class RewriteValve extends ValveBase {
+ if (rewritten) {
+ if (!done) {
+ // See if we need to replace the query string
+- String urlStringDecoded = urlDecoded.toString();
+- String queryStringDecoded = null;
+- int queryIndex = urlStringDecoded.indexOf('?');
++ String urlStringRewriteEncoded = urlRewriteEncoded.toString();
++ String queryStringRewriteEncoded = null;
++ int queryIndex = urlStringRewriteEncoded.indexOf('?');
+ if (queryIndex != -1) {
+- queryStringDecoded = urlStringDecoded.substring(queryIndex + 1);
+- urlStringDecoded = urlStringDecoded.substring(0, queryIndex);
++ queryStringRewriteEncoded = urlStringRewriteEncoded.substring(queryIndex + 1);
++ urlStringRewriteEncoded = urlStringRewriteEncoded.substring(0, queryIndex);
+ }
+ // Save the current context path before re-writing starts
+ String contextPath = null;
+@@ -489,22 +539,24 @@ public class RewriteValve extends ValveBase {
+ // This is neither decoded nor normalized
+ chunk.append(contextPath);
+ }
+- chunk.append(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
++
++ // Step 3. Complete the 2nd stage to encoding.
++ chunk.append(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
+ // Decoded and normalized URI
+ // Rewriting may have denormalized the URL
+- urlStringDecoded = RequestUtil.normalize(urlStringDecoded);
++ urlStringRewriteEncoded = RequestUtil.normalize(urlStringRewriteEncoded);
+ request.getCoyoteRequest().decodedURI().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
+ chunk = request.getCoyoteRequest().decodedURI().getCharChunk();
+ if (context) {
+ // This is decoded and normalized
+ chunk.append(request.getServletContext().getContextPath());
+ }
+- chunk.append(urlStringDecoded);
++ chunk.append(URLDecoder.decode(urlStringRewriteEncoded, uriCharset));
+ // Set the new Query if there is one
+- if (queryStringDecoded != null) {
++ if (queryStringRewriteEncoded != null) {
+ request.getCoyoteRequest().queryString().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
+ chunk = request.getCoyoteRequest().queryString().getCharChunk();
+- chunk.append(URLEncoder.QUERY.encode(queryStringDecoded, uriCharset));
++ chunk.append(REWRITE_QUERY_ENCODER.encode(queryStringRewriteEncoded, uriCharset));
+ if (qsa && originalQueryStringEncoded != null && originalQueryStringEncoded.length() > 0) {
+ chunk.append('&');
+ chunk.append(originalQueryStringEncoded);
+@@ -799,4 +851,31 @@ public class RewriteValve extends ValveBase {
+ throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
+ }
+ }
++
++
++ private CharSequence encodeForRewrite(CharSequence input) {
++ StringBuilder result = null;
++ int pos = 0;
++ int mark = 0;
++ while (pos < input.length()) {
++ char c = input.charAt(pos);
++ if (c == '%' || c == ';' || c == '?') {
++ if (result == null) {
++ result = new StringBuilder((int) (input.length() * 1.1));
++ }
++ result.append(input.subSequence(mark, pos));
++ result.append('%');
++ result.append(Character.forDigit((c >> 4) & 0xF, 16));
++ result.append(Character.forDigit(c & 0xF, 16));
++ mark = pos + 1;
++ }
++ pos++;
++ }
++ if (result != null) {
++ result.append(input.subSequence(mark, input.length()));
++ return result;
++ } else {
++ return input;
++ }
++ }
+ }
+diff --git a/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java b/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
+index 3a08886..5c737ae 100644
+--- a/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
++++ b/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
+@@ -20,6 +20,7 @@ import java.io.File;
+ import java.io.IOException;
+ import java.io.PrintWriter;
+ import java.net.HttpURLConnection;
++import java.net.URLDecoder;
+ import java.nio.charset.StandardCharsets;
+ import java.util.Arrays;
+ import java.util.HashMap;
+@@ -63,7 +64,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+
+ @Test
+ public void testBackslashPercentSign() throws Exception {
+- doTestRewrite("RewriteRule ^(.*) /a/\\%5A", "/", "/a/%255A");
++ doTestRewrite("RewriteRule ^(.*) /a/\\%5A", "/", "/a/%5A");
+ }
+
+ @Test
+@@ -142,7 +143,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+
+ @Test
+ public void testRewriteMap10() throws Exception {
+- doTestRewrite("RewriteMap lc int:escape\n" + "RewriteRule ^(.*) ${lc:$1}", "/c/a%20aa", "/c/a%2520aa");
++ doTestRewrite("RewriteMap lc int:escape\n" + "RewriteRule ^(.*) ${lc:$1}", "/c/a%20aa", "/c/a%20aa");
+ }
+
+ @Test
+@@ -346,7 +347,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testNonAsciiQueryStringWithB() throws Exception {
+ doTestRewrite("RewriteRule ^/b/(.*)/id=(.*) /c?filename=$1&id=$2 [B]",
+ "/b/file01/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95", "/c",
+- "filename=file01&id=%25E5%259C%25A8%25E7%25BA%25BF%25E6%25B5%258B%25E8%25AF%2595");
++ "filename=file01&id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
+ }
+
+
+@@ -354,8 +355,8 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testNonAsciiQueryStringAndPathAndRedirectWithB() throws Exception {
+ // Note the double encoding of the result (httpd produces the same result)
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*)/id=(.*) /c/$1?filename=$2&id=$3 [B,R]",
+- "/b/%E5%9C%A8%E7%BA%BF/file01/id=%E6%B5%8B%E8%AF%95", "/c/%25E5%259C%25A8%25E7%25BA%25BF",
+- "filename=file01&id=%25E6%25B5%258B%25E8%25AF%2595");
++ "/b/%E5%9C%A8%E7%BA%BF/file01/id=%E6%B5%8B%E8%AF%95", "/c/%E5%9C%A8%E7%BA%BF",
++ "filename=file01&id=%E6%B5%8B%E8%AF%95");
+ }
+
+
+@@ -371,7 +372,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testUtf8WithBothQsFlagsB() throws Exception {
+ // Note %C2%A1 == \u00A1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]", "/b/%C2%A1/id=%C2%A1?di=%C2%AE",
+- "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
++ "/c/%C2%A1%C2%A1", "id=%C2%A1");
+ }
+
+
+@@ -387,7 +388,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testUtf8WithBothQsFlagsRB() throws Exception {
+ // Note %C2%A1 == \u00A1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]", "/b/%C2%A1/id=%C2%A1?di=%C2%AE",
+- "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
++ "/c/%C2%A1%C2%A1", "id=%C2%A1");
+ }
+
+
+@@ -413,7 +414,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testUtf8WithBothQsFlagsBQSA() throws Exception {
+ // Note %C2%A1 == \u00A1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B,QSA]", "/b/%C2%A1/id=%C2%A1?di=%C2%AE",
+- "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1&di=%C2%AE");
++ "/c/%C2%A1%C2%A1", "id=%C2%A1&di=%C2%AE");
+ }
+
+
+@@ -429,7 +430,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ public void testUtf8WithBothQsFlagsRBQSA() throws Exception {
+ // Note %C2%A1 == \u00A1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,QSA]", "/b/%C2%A1/id=%C2%A1?di=%C2%AE",
+- "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1&di=%C2%AE");
++ "/c/%C2%A1%C2%A1", "id=%C2%A1&di=%C2%AE");
+ }
+
+
+@@ -461,7 +462,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8WithOriginalQsFlagsB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1",
++ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1",
+ "id=%C2%A1");
+ }
+
+@@ -476,7 +477,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8WithOriginalQsFlagsRB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1",
++ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1",
+ "id=%C2%A1");
+ }
+
+@@ -510,8 +511,8 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8WithRewriteQsFlagsB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]", "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1",
+- "id=%25C2%25A1");
++ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]", "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1",
++ "id=%C2%A1");
+ }
+
+
+@@ -534,8 +535,8 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8WithRewriteQsFlagsRB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]", "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1",
+- "id=%25C2%25A1");
++ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]", "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1",
++ "id=%C2%A1");
+ }
+
+
+@@ -575,7 +576,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8FlagsB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1", "/c/%C2%A1%25C2%25A1");
++ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1", "/c/%C2%A1%C2%A1");
+ }
+
+
+@@ -589,7 +590,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ @Test
+ public void testUtf8FlagsRB() throws Exception {
+ // Note %C2%A1 == \u00A1
+- doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1", "/c/%C2%A1%25C2%25A1");
++ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1", "/c/%C2%A1%C2%A1");
+ }
+
+
+@@ -784,6 +785,7 @@ public class TestRewriteValve extends TomcatBaseTest {
+ rewriteValve.setConfiguration(config);
+
+ Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
++ ctx.addServletMappingDecoded("/a/Z", "snoop");
+ ctx.addServletMappingDecoded("/a/%5A", "snoop");
+ ctx.addServletMappingDecoded("/c/*", "snoop");
+ ctx.addServletMappingDecoded("/W/*", "snoop");
+@@ -929,4 +931,87 @@ public class TestRewriteValve extends TomcatBaseTest {
+ }
+ }
+ }
++
++
++ @Test
++ public void testEncodedUriSimple() throws Exception {
++ doTestRewriteWithEncoding("aaa");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedQuestionMark01() throws Exception {
++ doTestRewriteWithEncoding("a%3fa");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedQuestionMark02() throws Exception {
++ doTestRewriteWithEncoding("%3faa");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedQuestionMark03() throws Exception {
++ doTestRewriteWithEncoding("aa%3f");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedQuestionMarkAndQueryString() throws Exception {
++ doTestRewriteWithEncoding("a%3fa?b=c", "a%3fa", "b=c");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedSemicolon01() throws Exception {
++ doTestRewriteWithEncoding("a%3ba");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedSemicolon02() throws Exception {
++ doTestRewriteWithEncoding("%3baa");
++ }
++
++
++ @Test
++ public void testEncodedUriEncodedSemicolon03() throws Exception {
++ doTestRewriteWithEncoding("aa%3b");
++ }
++
++
++ private void doTestRewriteWithEncoding(String segment) throws Exception {
++ doTestRewriteWithEncoding(segment, segment, null);
++ }
++
++ private void doTestRewriteWithEncoding(String segment, String expectedSegment, String expectedQueryString)
++ throws Exception {
++ Tomcat tomcat = getTomcatInstance();
++
++ // No file system docBase required
++ Context ctx = tomcat.addContext("", null);
++
++ RewriteValve rewriteValve = new RewriteValve();
++ tomcat.getHost().getPipeline().addValve(rewriteValve);
++
++ rewriteValve.setConfiguration("RewriteRule ^/source/(.*)$ /target/$1");
++
++ Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
++ ctx.addServletMappingDecoded("/target/*", "snoop");
++
++ tomcat.start();
++
++ ByteChunk res = new ByteChunk();
++ int rc = getUrl("http://localhost:" + getPort() + "/source/" + segment, res, false);
++
++ Assert.assertEquals(HttpServletResponse.SC_OK, rc);
++
++ res.setCharset(StandardCharsets.UTF_8);
++ String body = res.toString();
++ Assert.assertTrue(body, body.contains("REQUEST-URI: /target/" + expectedSegment));
++ Assert.assertTrue(body, body.contains("PATH-INFO: /" +
++ URLDecoder.decode(expectedSegment, StandardCharsets.UTF_8)));
++ Assert.assertTrue(body, body.contains("REQUEST-QUERY-STRING: " + expectedQueryString));
++ }
+ }
+diff --git a/webapps/docs/rewrite.xml b/webapps/docs/rewrite.xml
+index f153f9d..123d215 100644
+--- a/webapps/docs/rewrite.xml
++++ b/webapps/docs/rewrite.xml
+@@ -56,6 +56,28 @@
+
+
+
++
++
++ The URL presented to the rewrite valve is the same URL used for request
++ mapping with any literal '%', ';' and/or
++ '?' characters encoded in %nn form.
++
++ A rewrite rule that wishes to insert a literal '%',
++ ';', '?', '&' or '='
++ character should do so in %nn form. Other characters maybe
++ inserted in either literal or %nn form.
++
++ This enables the rewrite rules to:
++
++ - process URLs containing literal
'?' characters;
++ - add a query string;
++ - insert a literal
'%' character without it being confused with
++ %nn encoding.
++
++
++
++
++
+
+
+ The rewrite.config file contains a list of directives which closely
+--
+2.48.1
+
diff --git a/CVE-2025-31651-2.patch b/CVE-2025-31651-2.patch
new file mode 100644
index 0000000..52f0028
--- /dev/null
+++ b/CVE-2025-31651-2.patch
@@ -0,0 +1,24 @@
+From 175dc75fc428930034a6c93fb52f830d955d8e64 Mon Sep 17 00:00:00 2001
+From: Mark Thomas
+Date: Mon, 31 Mar 2025 17:34:05 +0100
+Subject: [PATCH] Fix back-port
+
+Origin: https://github.com/apache/tomcat/commit/175dc75fc428930034a6c93fb52f830d955d8e64
+
+---
+ java/org/apache/catalina/valves/rewrite/RewriteValve.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/java/org/apache/catalina/valves/rewrite/RewriteValve.java b/java/org/apache/catalina/valves/rewrite/RewriteValve.java
+index f049e5eb7e40..b4e73f6845b4 100644
+--- a/java/org/apache/catalina/valves/rewrite/RewriteValve.java
++++ b/java/org/apache/catalina/valves/rewrite/RewriteValve.java
+@@ -552,7 +552,7 @@ public void invoke(Request request, Response response) throws IOException, Servl
+ // This is decoded and normalized
+ chunk.append(request.getServletContext().getContextPath());
+ }
+- chunk.append(URLDecoder.decode(urlStringRewriteEncoded, uriCharset));
++ chunk.append(URLDecoder.decode(urlStringRewriteEncoded, uriCharset.name()));
+ // Set the new Query if there is one
+ if (queryStringRewriteEncoded != null) {
+ request.getCoyoteRequest().queryString().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
diff --git a/tomcat.spec b/tomcat.spec
index 77daf97..fe2cf55 100644
--- a/tomcat.spec
+++ b/tomcat.spec
@@ -23,7 +23,7 @@
Name: tomcat
Epoch: 1
Version: %{major_version}.%{minor_version}.%{micro_version}
-Release: 1
+Release: 2
Summary: Apache Servlet/JSP Engine, RI for Servlet %{servletspec}/JSP %{jspspec} API
License: Apache-2.0
@@ -51,6 +51,11 @@ Patch4: rhbz-1857043.patch
# remove bnd dependency which version is too low on rhel8
Patch6: remove-bnd-annotation.patch
Patch7: build-with-jdk-1.8.patch
+Patch8: CVE-2025-31650-1.patch
+Patch9: CVE-2025-31650-2.patch
+Patch10: CVE-2025-31650-3.patch
+Patch11: CVE-2025-31651-1.patch
+Patch12: CVE-2025-31651-2.patch
BuildArch: noarch
@@ -417,6 +422,9 @@ fi
%{appdir}/docs
%changelog
+* Tue Apr 29 2025 wangkai <13474090681@163.com> - 1:9.0.100-2
+- Fix CVE-2025-31650, CVE-2025-31651
+
* Fri Feb 21 2025 wangkai <13474090681@163.com> - 1:9.0.100-1
- Update to 9.0.100 for fix CVE-2024-56337
- Add a check to ensure that, if one or more web applications are potentially vulnerable to CVE-2024-56337,