This is an automated email from the ASF dual-hosted git repository.
markt 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 6b58e0213c Add support for encodedReverseSolidusHandling
6b58e0213c is described below
commit 6b58e0213c54091127ce3f785d2059c5ccd2131c
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Jan 21 16:43:38 2025 +0000
Add support for encodedReverseSolidusHandling
Defaults to the current behaviour which is decode (so allowBackslash
then controls what happens next).
---
java/org/apache/catalina/connector/Connector.java | 23 ++-
.../apache/catalina/connector/CoyoteAdapter.java | 3 +-
.../apache/tomcat/util/buf/LocalStrings.properties | 1 +
java/org/apache/tomcat/util/buf/UDecoder.java | 72 +++++---
test/org/apache/tomcat/util/buf/TestUDecoder.java | 189 ++++++++++++++++++++-
webapps/docs/changelog.xml | 8 +
webapps/docs/config/ajp.xml | 21 +++
webapps/docs/config/http.xml | 21 +++
8 files changed, 316 insertions(+), 22 deletions(-)
diff --git a/java/org/apache/catalina/connector/Connector.java
b/java/org/apache/catalina/connector/Connector.java
index 7685a4b5f9..00cc9a2315 100644
--- a/java/org/apache/catalina/connector/Connector.java
+++ b/java/org/apache/catalina/connector/Connector.java
@@ -272,7 +272,13 @@ public class Connector extends LifecycleMBeanBase {
/**
- * The behavior when an encoded solidus (slash) is submitted.
+ * The behavior when an encoded reverse solidus (backslash - \) is
submitted.
+ */
+ private EncodedSolidusHandling encodedReverseSolidusHandling =
EncodedSolidusHandling.DECODE;
+
+
+ /**
+ * The behavior when an encoded solidus (slash - /) is submitted.
*/
private EncodedSolidusHandling encodedSolidusHandling =
EncodedSolidusHandling.REJECT;
@@ -869,6 +875,21 @@ public class Connector extends LifecycleMBeanBase {
}
+ public String getEncodedReverseSolidusHandling() {
+ return encodedReverseSolidusHandling.getValue();
+ }
+
+
+ public void setEncodedReverseSolidusHandling(String
encodedReverseSolidusHandling) {
+ this.encodedReverseSolidusHandling =
EncodedSolidusHandling.fromString(encodedReverseSolidusHandling);
+ }
+
+
+ public EncodedSolidusHandling getEncodedReverseSolidusHandlingInternal() {
+ return encodedReverseSolidusHandling;
+ }
+
+
public String getEncodedSolidusHandling() {
return encodedSolidusHandling.getValue();
}
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java
b/java/org/apache/catalina/connector/CoyoteAdapter.java
index e0ed1b9b07..9abfebdb7c 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -635,7 +635,8 @@ public class CoyoteAdapter implements Adapter {
// %xx decoding of the URL
try {
req.getURLDecoder().convert(decodedURI.getByteChunk(),
- connector.getEncodedSolidusHandlingInternal());
+ connector.getEncodedSolidusHandlingInternal(),
+
connector.getEncodedReverseSolidusHandlingInternal());
} catch (IOException ioe) {
response.sendError(400,
sm.getString("coyoteAdapter.invalidURIWithMessage", ioe.getMessage()));
}
diff --git a/java/org/apache/tomcat/util/buf/LocalStrings.properties
b/java/org/apache/tomcat/util/buf/LocalStrings.properties
index 16d8352661..ed7dacc86e 100644
--- a/java/org/apache/tomcat/util/buf/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/buf/LocalStrings.properties
@@ -45,6 +45,7 @@ toStringUtil.classpath.unknown=Unknown - not an instance of
URLClassLoader
uDecoder.eof=End of file (EOF)
uDecoder.isHexDigit=The hexadecimal encoding is invalid
+uDecoder.noBackslash=The encoded backslash character is not allowed
uDecoder.noSlash=The encoded slash character is not allowed
uDecoder.urlDecode.conversionError=Failed to decode [{0}] using character set
[{1}]
uDecoder.urlDecode.missingDigit=Failed to decode [{0}] because the % character
must be followed by two hexadecimal digits
diff --git a/java/org/apache/tomcat/util/buf/UDecoder.java
b/java/org/apache/tomcat/util/buf/UDecoder.java
index b11965095e..3a1fa6edbf 100644
--- a/java/org/apache/tomcat/util/buf/UDecoder.java
+++ b/java/org/apache/tomcat/util/buf/UDecoder.java
@@ -58,10 +58,12 @@ public final class UDecoder {
/** %-encoded slash is forbidden in resource path */
private static final IOException EXCEPTION_SLASH = new
DecodeException(sm.getString("uDecoder.noSlash"));
+ /** %-encoded backslash is forbidden in resource path */
+ private static final IOException EXCEPTION_BACKSLASH = new
DecodeException(sm.getString("uDecoder.noBackslash"));
/**
* URLDecode, will modify the source. Assumes source bytes are encoded
using a superset of US-ASCII as per RFC 7230.
- * "%2f" will be rejected unless the input is a query string.
+ * "%5c" will be decoded. "%2f" will be rejected unless the input is a
query string.
*
* @param mb The URL encoded bytes
* @param query {@code true} if this is a query string. For a query string
'+' will be decoded to ' '
@@ -70,9 +72,9 @@ public final class UDecoder {
*/
public void convert(ByteChunk mb, boolean query) throws IOException {
if (query) {
- convert(mb, true, EncodedSolidusHandling.DECODE);
+ convert(mb, true, EncodedSolidusHandling.DECODE,
EncodedSolidusHandling.DECODE);
} else {
- convert(mb, false, EncodedSolidusHandling.REJECT);
+ convert(mb, false, EncodedSolidusHandling.REJECT,
EncodedSolidusHandling.DECODE);
}
}
@@ -85,14 +87,35 @@ public final class UDecoder {
* parameter will be ignored and the %2f
sequence will be decoded
*
* @throws IOException Invalid %xx URL encoding
+ *
+ * @deprecated Unused. Will be removed in Tomcat 12. Use
+ * {@link #convert(ByteChunk, EncodedSolidusHandling,
EncodedSolidusHandling)}
*/
+ @Deprecated
public void convert(ByteChunk mb, EncodedSolidusHandling
encodedSolidusHandling) throws IOException {
- convert(mb, false, encodedSolidusHandling);
+ convert(mb, false, encodedSolidusHandling,
EncodedSolidusHandling.DECODE);
}
- private void convert(ByteChunk mb, boolean query, EncodedSolidusHandling
encodedSolidusHandling)
- throws IOException {
+ /**
+ * URLDecode, will modify the source. Assumes source bytes are encoded
using a superset of US-ASCII as per RFC 7230.
+ *
+ * @param mb The URL encoded bytes
+ * @param encodedSolidusHandling How should the %2f sequence
handled by the decoder? For query strings this
+ * parameter will be ignored and
the %2f sequence will be decoded
+ * @param encodedReverseSolidusHandling How should the %5c sequence
handled by the decoder? For query strings this
+ * parameter will be ignored and
the %5c sequence will be decoded
+ *
+ * @throws IOException Invalid %xx URL encoding
+ */
+ public void convert(ByteChunk mb, EncodedSolidusHandling
encodedSolidusHandling,
+ EncodedSolidusHandling encodedReverseSolidusHandling) throws
IOException {
+ convert(mb, false, encodedSolidusHandling,
encodedReverseSolidusHandling);
+ }
+
+
+ private void convert(ByteChunk mb, boolean query, EncodedSolidusHandling
encodedSolidusHandling,
+ EncodedSolidusHandling encodedReverseSolidusHandling) throws
IOException {
int start = mb.getStart();
byte buff[] = mb.getBytes();
@@ -145,22 +168,33 @@ public final class UDecoder {
buff[idx] = buff[j];
}
}
+ } else if (res == '\\') {
+ switch (encodedReverseSolidusHandling) {
+ case DECODE: {
+ buff[idx] = (byte) res;
+ break;
+ }
+ case REJECT: {
+ throw EXCEPTION_BACKSLASH;
+ }
+ case PASS_THROUGH: {
+ buff[idx++] = buff[j - 2];
+ buff[idx++] = buff[j - 1];
+ buff[idx] = buff[j];
+ }
+ }
} else if (res == '%') {
/*
- * If encoded '/' is going to be left encoded then so must
encoded '%' else the subsequent %nn
- * decoding will either fail or corrupt the output.
+ * If encoded '/' or '\' is going to be left encoded then
so must encoded '%' else the subsequent
+ * %nn decoding will either fail or corrupt the output.
*/
- switch (encodedSolidusHandling) {
- case DECODE:
- case REJECT: {
- buff[idx] = (byte) res;
- break;
- }
- case PASS_THROUGH: {
- buff[idx++] = buff[j - 2];
- buff[idx++] = buff[j - 1];
- buff[idx] = buff[j];
- }
+ if
(encodedSolidusHandling.equals(EncodedSolidusHandling.PASS_THROUGH) ||
+
encodedReverseSolidusHandling.equals(EncodedSolidusHandling.PASS_THROUGH)) {
+ buff[idx++] = buff[j - 2];
+ buff[idx++] = buff[j - 1];
+ buff[idx] = buff[j];
+ } else {
+ buff[idx] = (byte) res;
}
} else {
buff[idx] = (byte) res;
diff --git a/test/org/apache/tomcat/util/buf/TestUDecoder.java
b/test/org/apache/tomcat/util/buf/TestUDecoder.java
index 94dbd14fb6..3fcecd0852 100644
--- a/test/org/apache/tomcat/util/buf/TestUDecoder.java
+++ b/test/org/apache/tomcat/util/buf/TestUDecoder.java
@@ -276,7 +276,194 @@ public class TestUDecoder {
bc.setCharset(StandardCharsets.UTF_8);
UDecoder udecoder = new UDecoder();
- udecoder.convert(bc, solidusHandling);
+ udecoder.convert(bc, solidusHandling, EncodedSolidusHandling.DECODE);
+
+ return bc.toString();
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus01() throws IOException {
+ doTestReverseSolidus("xxxxxx", "xxxxxx");
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus02() throws IOException {
+ doTestReverseSolidus("%20xxxx", " xxxx");
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus03() throws IOException {
+ doTestReverseSolidus("xx%20xx", "xx xx");
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus04() throws IOException {
+ doTestReverseSolidus("xxxx%20", "xxxx ");
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus05a() throws IOException {
+ doTestReverseSolidus("%5cxxxx", EncodedSolidusHandling.REJECT);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus05b() throws IOException {
+ String result = doTestReverseSolidus("%5cxxxx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("%5cxxxx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus05c() throws IOException {
+ String result = doTestReverseSolidus("%5cxxxx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("\\xxxx", result);
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus06a() throws IOException {
+ doTestReverseSolidus("%5cxx%20xx", EncodedSolidusHandling.REJECT);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus06b() throws IOException {
+ String result = doTestReverseSolidus("%5cxx%20xx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("%5cxx xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus06c() throws IOException {
+ String result = doTestReverseSolidus("%5cxx%20xx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("\\xx xx", result);
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus07a() throws IOException {
+ doTestReverseSolidus("xx%5c%20xx", EncodedSolidusHandling.REJECT);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus07b() throws IOException {
+ String result = doTestReverseSolidus("xx%5c%20xx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("xx%5c xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus07c() throws IOException {
+ String result = doTestReverseSolidus("xx%5c%20xx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("xx\\ xx", result);
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus08a() throws IOException {
+ doTestReverseSolidus("xx%20%5cxx", EncodedSolidusHandling.REJECT);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus08b() throws IOException {
+ String result = doTestReverseSolidus("xx%20%5cxx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("xx %5cxx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus08c() throws IOException {
+ String result = doTestReverseSolidus("xx%20%5cxx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("xx \\xx", result);
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus09a() throws IOException {
+ doTestReverseSolidus("xx%20xx%5c", EncodedSolidusHandling.REJECT);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus09b() throws IOException {
+ String result = doTestReverseSolidus("xx%20xx%5c",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("xx xx%5c", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus09c() throws IOException {
+ String result = doTestReverseSolidus("xx%20xx%5c",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("xx xx\\", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus10a() throws IOException {
+ String result = doTestReverseSolidus("xx%25xx",
EncodedSolidusHandling.REJECT);
+ Assert.assertEquals("xx%xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus10b() throws IOException {
+ String result = doTestReverseSolidus("xx%25xx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("xx%25xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus10c() throws IOException {
+ String result = doTestReverseSolidus("xx%25xx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("xx%xx", result);
+ }
+
+
+ @Test(expected = CharConversionException.class)
+ public void testURLDecodeStringReverseSolidus11a() throws IOException {
+ String result = doTestReverseSolidus("xx%5c%25xx",
EncodedSolidusHandling.REJECT);
+ Assert.assertEquals("xx%xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus11b() throws IOException {
+ String result = doTestReverseSolidus("xx%5c%25xx",
EncodedSolidusHandling.PASS_THROUGH);
+ Assert.assertEquals("xx%5c%25xx", result);
+ }
+
+
+ @Test
+ public void testURLDecodeStringReverseSolidus11c() throws IOException {
+ String result = doTestReverseSolidus("xx%5c%25xx",
EncodedSolidusHandling.DECODE);
+ Assert.assertEquals("xx\\%xx", result);
+ }
+
+
+ private void doTestReverseSolidus(String input, String expected) throws
IOException {
+ for (EncodedSolidusHandling solidusHandling :
EncodedSolidusHandling.values()) {
+ String result = doTestReverseSolidus(input, solidusHandling);
+ Assert.assertEquals(expected, result);
+ }
+ }
+
+
+ private String doTestReverseSolidus(String input, EncodedSolidusHandling
reverseSolidusHandling) throws IOException {
+ byte[] b = input.getBytes(StandardCharsets.UTF_8);
+ ByteChunk bc = new ByteChunk(16);
+ bc.setBytes(b, 0, b.length);
+ bc.setCharset(StandardCharsets.UTF_8);
+
+ UDecoder udecoder = new UDecoder();
+ udecoder.convert(bc, EncodedSolidusHandling.REJECT,
reverseSolidusHandling);
return bc.toString();
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 58db4a3865..171801fcd8 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -187,6 +187,14 @@
completed to aid GC and reduce the size of the HTTP/2 recycled request
and response cache. (markt)
</scode>
+ <add>
+ Add a new Connector configuration attribute,
+ <code>encodedReverseSolidusHandling</code>, to control how
+ <code>%5c</code> sequences in URLs are handled. The default behaviour
is
+ unchanged (decode) keeping mind mind that the
+ <strong>allowBackslash</strong> attributes determines how the decoded
+ URI is processed. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Jasper">
diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
index b6590e59ff..cdb3d2c83f 100644
--- a/webapps/docs/config/ajp.xml
+++ b/webapps/docs/config/ajp.xml
@@ -120,6 +120,27 @@
By default, DNS lookups are disabled.</p>
</attribute>
+ <attribute name="encodedReverseSolidusHandling" required="false">
+ <p>When set to <code>reject</code> request paths containing a
+ <code>%5c</code> sequence will be rejected with a 400 response. When set
+ to <code>decode</code> request paths containing a <code>%5c</code>
+ sequence will have that sequence decoded to <code>\</code> at the same
+ time other <code>%nn</code> sequences are decoded. When set to
+ <code>passthrough</code> request paths containing a <code>%5c</code>
+ sequence will be processed with the <code>%5c</code> sequence unchanged.
+ </p>
+ <p>When set to <code>decoded</code>, the <strong>allowBackslash</strong>
+ attribute will be applied after decoding.
+ </p>
+ <p>If <code>passthrough</code> is used then it is the application's
+ resposibility to perform any further <code>%nn</code> decoding required.
+ Any <code>%25</code> sequences (encoded <code>%</code>) in the request
+ path with also be processed with the <code>%25</code> sequence unchanged
+ to avoid potential corruption and/or decoding failure when the path is
+ subsequently <code>%nn</code> decoded by the application.</p>
+ <p>If not specified, the default value is <code>decode</code>.</p>
+ </attribute>
+
<attribute name="encodedSolidusHandling" required="false">
<p>When set to <code>reject</code> request paths containing a
<code>%2f</code> sequence will be rejected with a 400 response. When set
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index e803e25823..ecd31b7e24 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -116,6 +116,27 @@
By default, DNS lookups are disabled.</p>
</attribute>
+ <attribute name="encodedReverseSolidusHandling" required="false">
+ <p>When set to <code>reject</code> request paths containing a
+ <code>%5c</code> sequence will be rejected with a 400 response. When set
+ to <code>decode</code> request paths containing a <code>%5c</code>
+ sequence will have that sequence decoded to <code>\</code> at the same
+ time other <code>%nn</code> sequences are decoded. When set to
+ <code>passthrough</code> request paths containing a <code>%5c</code>
+ sequence will be processed with the <code>%5c</code> sequence unchanged.
+ </p>
+ <p>When set to <code>decoded</code>, the <strong>allowBackslash</strong>
+ attribute will be applied after decoding.
+ </p>
+ <p>If <code>passthrough</code> is used then it is the application's
+ resposibility to perform any further <code>%nn</code> decoding required.
+ Any <code>%25</code> sequences (encoded <code>%</code>) in the request
+ path with also be processed with the <code>%25</code> sequence unchanged
+ to avoid potential corruption and/or decoding failure when the path is
+ subsequently <code>%nn</code> decoded by the application.</p>
+ <p>If not specified, the default value is <code>decode</code>.</p>
+ </attribute>
+
<attribute name="encodedSolidusHandling" required="false">
<p>When set to <code>reject</code> request paths containing a
<code>%2f</code> sequence will be rejected with a 400 response. When set
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]