This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 22b61b4d0f Ignore empty list elements when parsing entity-tag lists
(#1150)
22b61b4d0f is described below
commit 22b61b4d0f3fb31c5b241c0cdbb43ea0667d6eec
Author: Arturo Bernal <[email protected]>
AuthorDate: Fri Nov 13 15:50:56 2026 +0100
Ignore empty list elements when parsing entity-tag lists (#1150)
RFC 9110, section 5.6.1.2 requires a recipient to parse and ignore a
reasonable number of empty list elements, and If-Match and If-None-Match
are defined as lists of entity-tags that may contain no element. A value
such as "a",, "b", one with a leading or trailing comma, or one with no
entity-tag at all was rejected with a 400 response. Ignore the empty
elements. A list with no entity-tag is valid but nothing in it can match.
Malformed entity-tags are still rejected.
The test expectations that required a 400 for these values are updated.
---
.../apache/tomcat/util/http/parser/EntityTag.java | 15 ++---
.../TestDefaultServletIfMatchRequests.java | 26 ++++++--
.../TestDefaultServletRfc9110Section13.java | 13 +++-
.../tomcat/util/http/parser/TestEntityTag.java | 70 ++++++++++++++++++++++
webapps/docs/changelog.xml | 4 ++
5 files changed, 115 insertions(+), 13 deletions(-)
diff --git a/java/org/apache/tomcat/util/http/parser/EntityTag.java
b/java/org/apache/tomcat/util/http/parser/EntityTag.java
index 5aa7dae311..9b095348b3 100644
--- a/java/org/apache/tomcat/util/http/parser/EntityTag.java
+++ b/java/org/apache/tomcat/util/http/parser/EntityTag.java
@@ -32,8 +32,8 @@ public class EntityTag {
}
/**
- * Parse the given input as (per RFC 7232) 1#entity-tag. Compare an ETag
header with a resource ETag as described in
- * RFC 7232 section 2.3.2.
+ * Parse the given input as (per RFC 9110) #entity-tag. Compare an ETag
header with a resource ETag as described in
+ * RFC 9110 section 8.8.3.2.
*
* @param input The input to parse
* @param compareWeak Use weak comparison e.g. match "etag" with W/"etag"
@@ -61,12 +61,15 @@ public class EntityTag {
while (true) {
boolean strong = false;
- HttpParser.skipWhitespace(input);
+ // Empty list elements must be ignored (RFC 9110, section 5.6.1.2)
+ while (HttpParser.skipConstant(input, ",") == SkipResult.FOUND) {
+ // Skip
+ }
switch (HttpParser.skipConstant(input, "W/")) {
case EOF:
- // Empty values are invalid
- return null;
+ // A list that contains no entity-tag is valid but nothing
in it can match
+ return result;
case NOT_FOUND:
strong = true;
break;
@@ -89,8 +92,6 @@ public class EntityTag {
}
}
- HttpParser.skipWhitespace(input);
-
switch (HttpParser.skipConstant(input, ",")) {
case EOF:
return result;
diff --git
a/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java
b/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java
index 40815a3b7d..09c0a7e41d 100644
--- a/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java
+++ b/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java
@@ -77,17 +77,33 @@ public class TestDefaultServletIfMatchRequests extends
TomcatBaseTest {
parameterSets.add(new Object[] { resourceWithStrongETag, null,
RC_200, RC_200 });
// match header is invalid
- parameterSets.add(new Object[] { resourceWithStrongETag, "",
RC_400, RC_400 });
parameterSets.add(new Object[] { resourceWithStrongETag, "W",
RC_400, RC_400 });
parameterSets.add(new Object[] { resourceWithStrongETag, "W/",
RC_400, RC_400 });
parameterSets.add(new Object[] { resourceWithStrongETag, "w/" +
resourceETagStrong, RC_400, RC_400 });
parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagStrong + " x", RC_400, RC_400 });
parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagStrong + "x", RC_400, RC_400 });
+
+ // match header is a list that contains no entity tag (RFC 9110,
section 5.6.1.2). It is valid but nothing
+ // in it can match.
+ parameterSets.add(new Object[] { resourceWithStrongETag, "",
RC_412, RC_200 });
+ for (String concat : CONCAT) {
+ parameterSets.add(new Object[] { resourceWithStrongETag,
concat, RC_412, RC_200 });
+ }
+
+ // empty list elements are ignored (RFC 9110, section 5.6.1.2) so
the result is the same as without them
+ // The strong tag only matches a resource that has a strong tag.
The weak tag never matches for If-Match.
+ Integer rcIfMatchStrongTag = resourceWithStrongETag.booleanValue()
? RC_200 : RC_412;
+ Integer rcIfNoneMatchStrongTag =
resourceWithStrongETag.booleanValue() ? RC_304 : RC_200;
+ Integer rcIfNoneMatchWeakTag =
resourceWithStrongETag.booleanValue() ? RC_200 : RC_304;
for (String concat : CONCAT) {
- parameterSets.add(new Object[] { resourceWithStrongETag,
concat + resourceETagStrong, RC_400, RC_400 });
- parameterSets.add(new Object[] { resourceWithStrongETag,
concat + resourceETagWeak, RC_400, RC_400 });
- parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagStrong + concat, RC_400, RC_400 });
- parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagWeak + concat, RC_400, RC_400 });
+ parameterSets.add(new Object[] { resourceWithStrongETag,
concat + resourceETagStrong,
+ rcIfMatchStrongTag, rcIfNoneMatchStrongTag });
+ parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagStrong + concat,
+ rcIfMatchStrongTag, rcIfNoneMatchStrongTag });
+ parameterSets.add(new Object[] { resourceWithStrongETag,
concat + resourceETagWeak, RC_412,
+ rcIfNoneMatchWeakTag });
+ parameterSets.add(new Object[] { resourceWithStrongETag,
resourceETagWeak + concat, RC_412,
+ rcIfNoneMatchWeakTag });
}
// match header always matches resource (leading and trailing
space should be ignored)
diff --git
a/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
b/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
index 24f6baa99b..6f3d63d5d2 100644
--- a/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
+++ b/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
@@ -75,6 +75,12 @@ public class TestDefaultServletRfc9110Section13 extends
TomcatBaseTest {
List<Object[]> parameterSets = new ArrayList<>();
for (Boolean useStrongEtag : booleans) {
for (Task task : Arrays.asList(Task.HEAD_INDEX_HTML,
Task.GET_INDEX_HTML, Task.POST_INDEX_HTML)) {
+ // RFC 9110, Section 5.6.1.2: a list that contains no
entity-tag is valid and nothing in it can match
+ parameterSets.add(new Object[] { useStrongEtag, task,
EtagPrecondition.EMPTY, null, null, null, null,
+ null, Boolean.FALSE, SC_412 });
+ parameterSets.add(new Object[] { useStrongEtag, task, null,
null, EtagPrecondition.EMPTY, null, null,
+ null, Boolean.FALSE, SC_200 });
+
// RFC 9110, Section 13.2.2, Step 1, HEAD: If-Match with and
without If-Unmodified-Since
for (DatePrecondition dateCondition :
DatePrecondition.values()) {
parameterSets.add(new Object[] { useStrongEtag, task,
EtagPrecondition.ALL, dateCondition, null,
@@ -407,7 +413,8 @@ public class TestDefaultServletRfc9110Section13 extends
TomcatBaseTest {
ALL,
NOT_IN,
INVALID,
- INVALID_ALL_PLUS_OTHER
+ INVALID_ALL_PLUS_OTHER,
+ EMPTY
}
@@ -478,6 +485,10 @@ public class TestDefaultServletRfc9110Section13 extends
TomcatBaseTest {
headerValues.add("*");
headerValues.add("W/\"1abcd\"");
break;
+ case EMPTY:
+ // A list with no elements is valid (RFC 9110, section
5.6.1.2) and nothing in it can match
+ headerValues.add("");
+ break;
}
if (!headerValues.isEmpty()) {
requestHeaders.put(headerName, headerValues);
diff --git a/test/org/apache/tomcat/util/http/parser/TestEntityTag.java
b/test/org/apache/tomcat/util/http/parser/TestEntityTag.java
new file mode 100644
index 0000000000..dcd0886a79
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/parser/TestEntityTag.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.parser;
+
+import java.io.StringReader;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestEntityTag {
+
+ @Test
+ public void testMatch() throws Exception {
+ Assert.assertEquals(Boolean.TRUE, compare("\"a\", W/\"b\"", true,
"\"b\""));
+ Assert.assertEquals(Boolean.FALSE, compare("\"a\", W/\"b\"", false,
"\"b\""));
+ Assert.assertEquals(Boolean.FALSE, compare("\"a\", \"b\"", false,
"\"c\""));
+ }
+
+
+ @Test
+ public void testEmptyListElements() throws Exception {
+ // RFC 9110, section 5.6.1.2: empty list elements must be ignored
+ String[] headers = new String[] { "\"a\",, \"b\"", "\"a\", , \"b\"",
",\"a\", \"b\"", "\"a\", \"b\",",
+ "\"a\", \"b\" ,, , " };
+ for (String header : headers) {
+ Assert.assertEquals(header, Boolean.TRUE, compare(header, false,
"\"b\""));
+ Assert.assertEquals(header, Boolean.FALSE, compare(header, false,
"\"c\""));
+ }
+ }
+
+
+ @Test
+ public void testNoEntityTags() throws Exception {
+ // RFC 9110, section 5.6.1.2: a list that contains no elements is
valid and nothing in it can match
+ String[] headers = new String[] { "", " ", ",", " , ,, " };
+ for (String header : headers) {
+ Assert.assertEquals("[" + header + "]", Boolean.FALSE,
compare(header, false, "\"a\""));
+ Assert.assertEquals("[" + header + "]", Boolean.FALSE,
compare(header, true, "W/\"a\""));
+ }
+ }
+
+
+ @Test
+ public void testInvalid() throws Exception {
+ Assert.assertNull(compare("a", false, "\"a\""));
+ Assert.assertNull(compare("\"a\" \"b\"", false, "\"a\""));
+ Assert.assertNull(compare("\"a\", b", false, "\"a\""));
+ Assert.assertNull(compare("\"a\",, b", false, "\"a\""));
+ Assert.assertNull(compare("W/", false, "\"a\""));
+ }
+
+
+ private Boolean compare(String header, boolean compareWeak, String
resourceETag) throws Exception {
+ return EntityTag.compareEntityTag(new StringReader(header),
compareWeak, resourceETag);
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b1e303df43..b88392a6d3 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -166,6 +166,10 @@
section 15.2). Pull request <pr>1149</pr> provided by Arturo Bernal.
(markt)
</fix>
+ <fix>
+ Skip empty elements when parsing entity tag lists. Pull request
+ <pr>1150</pr> provided by Arturo Bernal. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]