On 28/02/2025 22:41, r...@apache.org wrote:
This is an automated email from the ASF dual-hosted git repository.

remm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
      new 7f0df68d29 69602: Allow weak etags in If-Range header
7f0df68d29 is described below

commit 7f0df68d292bcb576a48b975f169010b911d728c
Author: remm <r...@apache.org>
AuthorDate: Fri Feb 28 23:40:42 2025 +0100

     69602: Allow weak etags in If-Range header
Adjust with the specification language, '"' in the first three chars
     should detect if this should be a date or an etag.
     Trim first.
---
  .../apache/catalina/servlets/DefaultServlet.java   | 38 +++++++++++++---------
  .../servlets/TestDefaultServletRangeRequests.java  | 36 ++++++++++++++++++--
  webapps/docs/changelog.xml                         |  5 +++
  3 files changed, 60 insertions(+), 19 deletions(-)

diff --git a/java/org/apache/catalina/servlets/DefaultServlet.java 
b/java/org/apache/catalina/servlets/DefaultServlet.java
index 0e4a33e50c..9fb75069f1 100644
--- a/java/org/apache/catalina/servlets/DefaultServlet.java
+++ b/java/org/apache/catalina/servlets/DefaultServlet.java
@@ -2376,40 +2376,46 @@ public class DefaultServlet extends HttpServlet {
              // If-Range is not present
              return true;
          }
-        String headerValue = headerEnum.nextElement();
+        String headerValue = headerEnum.nextElement().trim();
          if (headerEnum.hasMoreElements()) {
              // Multiple If-Range headers
              response.sendError(HttpServletResponse.SC_BAD_REQUEST);
              return false;
          }
- long headerValueTime = -1L;
-        try {
-            headerValueTime = request.getDateHeader("If-Range");
-        } catch (IllegalArgumentException e) {
-            // Ignore
-        }
-
-        if (headerValueTime == -1L) {
-            // Not HTTP-date so this should be a single strong etag
-            if (headerValue.length() < 2 || headerValue.charAt(0) != '"' ||
+        if (headerValue.length() > 2 && (headerValue.charAt(0) == '"' || 
headerValue.charAt(2) == '"')) {
+            boolean weakETag = headerValue.startsWith("W/\"");
+            if ((!weakETag && headerValue.charAt(0) != '"') ||
                      headerValue.charAt(headerValue.length() - 1) != '"' ||
-                    headerValue.indexOf('"', 1) != headerValue.length() - 1) {
-                // Not a single, strong entity tag
+                    headerValue.indexOf('"', weakETag ? 3 : 1) != 
headerValue.length() - 1) {
+                // Not a single entity tag
                  response.sendError(HttpServletResponse.SC_BAD_REQUEST);
                  return false;
              }
              // If the ETag the client gave does not match the entity
              // etag, then the entire entity is returned.
-            if (resourceETag != null && resourceETag.startsWith("\"") && 
resourceETag.equals(headerValue.trim())) {
+            if (resourceETag != null && resourceETag.equals(headerValue)) {

This doesn't look right. Weak entity tags should never match for a request using If-Range. I think you need "... && !weakETag" in the above condition as well.

Mark



                  return true;
              } else {
                  return false;
              }
          } else {
-            // unit of HTTP date is second, ignore millisecond part.
-            return resourceLastModified >= headerValueTime && 
resourceLastModified < headerValueTime + 1000;
+            long headerValueTime = -1L;
+            try {
+                headerValueTime = request.getDateHeader("If-Range");
+            } catch (IllegalArgumentException e) {
+                // Ignore
+            }
+            if (headerValueTime >= 0) {
+                // unit of HTTP date is second, ignore millisecond part.
+                return resourceLastModified >= headerValueTime && 
resourceLastModified < headerValueTime + 1000;
+            } else {
+                // Not a single entity tag and not a valid date either
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                return false;
+            }
          }
+
      }
/**
diff --git 
a/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java 
b/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java
index af6031a159..4fb3952531 100644
--- a/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java
+++ b/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java
@@ -16,7 +16,10 @@
   */
  package org.apache.catalina.servlets;
+import java.io.ByteArrayOutputStream;
  import java.io.File;
+import java.io.FileInputStream;
+import java.security.MessageDigest;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
@@ -30,9 +33,12 @@ import org.junit.runners.Parameterized;
  import org.junit.runners.Parameterized.Parameter;
import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
  import org.apache.catalina.startup.Tomcat;
  import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.catalina.util.IOTools;
  import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
  import org.apache.tomcat.util.http.FastHttpDateFormat;
@RunWith(Parameterized.class)
@@ -47,6 +53,14 @@ public class TestDefaultServletRangeRequests extends 
TomcatBaseTest {
          long len = index.length();
          String strLen = Long.toString(len);
          String lastModified = 
FastHttpDateFormat.formatDate(index.lastModified());
+        String weakETag = "W/\"" + strLen + "-" + Long.toString(index.lastModified()) + 
"\"";
+        String strongETag = "\"\"";
+        try (FileInputStream is = new FileInputStream(index)) {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            IOTools.flow(is, os);
+            strongETag = "\"" + 
HexUtils.toHexString(MessageDigest.getInstance("SHA-1").digest(os.toByteArray())) + "\"";
+        } catch (Exception e) {
+        }
List<Object[]> parameterSets = new ArrayList<>(); @@ -98,11 +112,24 @@ public class TestDefaultServletRangeRequests extends TomcatBaseTest {
          parameterSets.add(new Object[] {
                  "bytes=0-9", lastModified, Integer.valueOf(206), "10", "0-9/" 
+ len });
          // Nonsense data (request rejected)
+        parameterSets.add(new Object[] { "bytes=0-9", "a-b-c", Integer.valueOf(400), 
null, "" });
+        parameterSets.add(new Object[] { "bytes=0-9", "W\"123\"", Integer.valueOf(400), 
null, "" });
+        parameterSets.add(new Object[] { "bytes=0-9", "\"123\"W", Integer.valueOf(400), 
null, "" });
+        // More than one (request rejected)
          parameterSets.add(new Object[] {
-                "bytes=0-9", "a-b-c", Integer.valueOf(400), null, ""});
+                "bytes=0-9", "\"46273648\", " + weakETag, Integer.valueOf(400), null, 
"" });
          // Different date (return whole entity)
          parameterSets.add(new Object[] {
-                "bytes=0-9", FastHttpDateFormat.formatDate(1000), Integer.valueOf(200), 
strLen, ""});
+                "bytes=0-9", FastHttpDateFormat.formatDate(1000), Integer.valueOf(200), 
strLen, "" });
+        // Valid weak etag
+        parameterSets.add(new Object[] {
+                "bytes=0-9", weakETag, Integer.valueOf(206), "10", "0-9/" + 
len });
+        // Invalid strong etag
+        parameterSets.add(new Object[] {
+                "bytes=0-9", "\"46273648\"", Integer.valueOf(200), strLen, "" 
});
+        // Valid strong etag
+        parameterSets.add(new Object[] {
+                "bytes=0-9", strongETag, Integer.valueOf(206), "10", "0-9/" + 
len });
return parameterSets;
      }
@@ -126,7 +153,10 @@ public class TestDefaultServletRangeRequests extends 
TomcatBaseTest {
          File appDir = new File("test/webapp");
          Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
- Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName());
+        Wrapper wrapper = Tomcat.addServlet(ctxt, "default", 
DefaultServlet.class.getName());
+        if (ifRangeHeader != null && ifRangeHeader.startsWith("\"")) {
+            wrapper.addInitParameter("useStrongETags", "true");
+        }
          ctxt.addServletMappingDecoded("/", "default");
tomcat.start();
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7c3c7baf41..8b39743e1d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -170,6 +170,11 @@
          Avoid scenarios where temporary files used for partial PUT would not
          be deleted. (remm)
        </fix>
+      <fix>
+        <bug>69602</bug>: Fix regression in releases from 12-2024 that were too
+        strict and rejected weak etags in the <code>If-Range</code> header.
+        (remm)
+      </fix>
      </changelog>
    </subsection>
    <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to