This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit ac830b523cbd8abe10c593d1abb4ddc496c69d50 Author: opencode <[email protected]> AuthorDate: Wed Oct 14 11:26:09 2026 +0200 Recycle C2BConverter in UEncoder.encodeURL to avoid state leaking across calls UEncoder.encodeURL() recycled the internal byte and char chunks but not the per-instance C2BConverter. C2BConverter retains characters that could not yet be encoded (such as a high surrogate not followed by a low surrogate) in a private leftovers buffer. A string ending with such a character therefore left it in the converter, and the next encodeURL() call on the same instance silently paired the leftover with the start of the new, unrelated string, corrupting the result. For example, encoding "ab\uD800" followed by "\uDC00cd" produced %f0%90%80%80cd (U+10000) instead of %3fcd. Fix this by calling c2b.recycle() whenever an existing converter is reused. C2BConverter.recycle() resets the charset encoder and clears the leftovers buffer. Add a regression test that compares the second call's result with that of a fresh encoder. --- java/org/apache/tomcat/util/buf/UEncoder.java | 5 +++++ test/org/apache/tomcat/util/buf/TestUEncoder.java | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/java/org/apache/tomcat/util/buf/UEncoder.java b/java/org/apache/tomcat/util/buf/UEncoder.java index ec9051b20f..038b9b2ddd 100644 --- a/java/org/apache/tomcat/util/buf/UEncoder.java +++ b/java/org/apache/tomcat/util/buf/UEncoder.java @@ -93,6 +93,11 @@ public final class UEncoder { bb.recycle(); cb.recycle(); output.recycle(); + // Recycle the converter as well. Otherwise, an unpaired surrogate + // left over from the previous call is retained in the converter's + // internal buffer and silently combined with the start of the + // next, unrelated string. + c2b.recycle(); } for (int i = start; i < end; i++) { diff --git a/test/org/apache/tomcat/util/buf/TestUEncoder.java b/test/org/apache/tomcat/util/buf/TestUEncoder.java index 73f3b13f38..d72510e68e 100644 --- a/test/org/apache/tomcat/util/buf/TestUEncoder.java +++ b/test/org/apache/tomcat/util/buf/TestUEncoder.java @@ -42,4 +42,26 @@ public class TestUEncoder { Assert.assertTrue(urlEncoder.encodeURL(s, 0, s.length()) .equals("%f0%90%90%81")); } + + + @Test + public void testEncodeURLUnpairedSurrogateDoesNotLeak() throws IOException { + UEncoder urlEncoder = new UEncoder(SafeCharsSet.DEFAULT); + + // A string ending with an unpaired high surrogate. The surrogate + // cannot be encoded. It must not be retained by the encoder and + // combined with the start of a later, unrelated string. + String loneHigh = "ab" + '\uD800'; + urlEncoder.encodeURL(loneHigh, 0, loneHigh.length()); + + String low = '\uDC00' + "cd"; + String result = urlEncoder.encodeURL(low, 0, low.length()).toString(); + + // The result must match that of a fresh encoder, i.e. the unpaired + // low surrogate must be replaced, not paired with the leftover high + // surrogate from the previous call + UEncoder fresh = new UEncoder(SafeCharsSet.DEFAULT); + Assert.assertEquals(fresh.encodeURL(low, 0, low.length()).toString(), + result); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
