This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new 1f5bad9423 Ignore empty list elements when parsing entity-tag lists 
(#1150)
1f5bad9423 is described below

commit 1f5bad94233b670180093a922ff9cfe7cc22ae19
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 701f3d1262..da89f8b337 100644
--- a/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
+++ b/test/org/apache/catalina/servlets/TestDefaultServletRfc9110Section13.java
@@ -76,6 +76,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,
@@ -408,7 +414,8 @@ public class TestDefaultServletRfc9110Section13 extends 
TomcatBaseTest {
         ALL,
         NOT_IN,
         INVALID,
-        INVALID_ALL_PLUS_OTHER
+        INVALID_ALL_PLUS_OTHER,
+        EMPTY
     }
 
 
@@ -479,6 +486,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 4f7b560957..42beb88c9e 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]

Reply via email to