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 4810b66087 Add strong ETag support
4810b66087 is described below

commit 4810b660870d6b42bab6b076f57d444cc30b8d8c
Author: remm <r...@apache.org>
AuthorDate: Tue Nov 19 23:24:27 2024 +0100

    Add strong ETag support
    
    Using a useStrongETags init parameter on the default (and WebDAV)
    servlet.
    The ETag generated is a SHA-1 checksum of the file content.
---
 java/org/apache/catalina/WebResource.java          | 13 +++++-
 .../apache/catalina/servlets/DefaultServlet.java   | 16 ++++++-
 .../catalina/webresources/AbstractResource.java    | 52 ++++++++++++++++++++++
 .../catalina/webresources/CachedResource.java      |  5 +++
 .../catalina/servlets/TestWebdavServlet.java       | 16 +++++++
 webapps/docs/changelog.xml                         |  6 +++
 6 files changed, 105 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/catalina/WebResource.java 
b/java/org/apache/catalina/WebResource.java
index d71af62f26..ba5adec5e6 100644
--- a/java/org/apache/catalina/WebResource.java
+++ b/java/org/apache/catalina/WebResource.java
@@ -92,13 +92,22 @@ public interface WebResource {
     String getWebappPath();
 
     /**
-     * Return the strong ETag if available (currently not supported) else 
return the weak ETag calculated from the
-     * content length and last modified.
+     * Return the weak ETag calculated from the content length and last 
modified.
      *
      * @return The ETag for this resource
      */
     String getETag();
 
+    /**
+     * Return the strong ETag if available else return the weak ETag 
calculated from the
+     * content length and last modified.
+     *
+     * @return The ETag for this resource
+     */
+    default String getStrongETag() {
+        return getETag();
+    }
+
     /**
      * Set the MIME type for this Resource.
      *
diff --git a/java/org/apache/catalina/servlets/DefaultServlet.java 
b/java/org/apache/catalina/servlets/DefaultServlet.java
index d288725b0d..c5e4c5a2c8 100644
--- a/java/org/apache/catalina/servlets/DefaultServlet.java
+++ b/java/org/apache/catalina/servlets/DefaultServlet.java
@@ -260,6 +260,11 @@ public class DefaultServlet extends HttpServlet {
      */
     private boolean allowPartialPut = true;
 
+    /**
+     * Use strong etags whenever possible.
+     */
+    private boolean useStrongETags = false;
+
 
     // --------------------------------------------------------- Public Methods
 
@@ -394,6 +399,11 @@ public class DefaultServlet extends HttpServlet {
         if (getServletConfig().getInitParameter("allowPartialPut") != null) {
             allowPartialPut = 
Boolean.parseBoolean(getServletConfig().getInitParameter("allowPartialPut"));
         }
+
+        if (getServletConfig().getInitParameter("useStrongETags") != null) {
+            useStrongETags = 
Boolean.parseBoolean(getServletConfig().getInitParameter("useStrongETags"));
+        }
+
     }
 
     private CompressionFormat[] parseCompressionFormats(String precompressed, 
String gzip) {
@@ -2229,7 +2239,11 @@ public class DefaultServlet extends HttpServlet {
      * @return The result of calling {@link WebResource#getETag()} on the 
given resource
      */
     protected String generateETag(WebResource resource) {
-        return resource.getETag();
+        if (useStrongETags) {
+            return resource.getStrongETag();
+        } else {
+            return resource.getETag();
+        }
     }
 
 
diff --git a/java/org/apache/catalina/webresources/AbstractResource.java 
b/java/org/apache/catalina/webresources/AbstractResource.java
index 384c327321..2bd0bdf356 100644
--- a/java/org/apache/catalina/webresources/AbstractResource.java
+++ b/java/org/apache/catalina/webresources/AbstractResource.java
@@ -16,13 +16,18 @@
  */
 package org.apache.catalina.webresources;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.security.MessageDigest;
 
 import org.apache.catalina.WebResource;
 import org.apache.catalina.WebResourceRoot;
+import org.apache.catalina.util.IOTools;
 import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.http.FastHttpDateFormat;
 import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.security.ConcurrentMessageDigest;
 
 public abstract class AbstractResource implements WebResource {
 
@@ -33,6 +38,7 @@ public abstract class AbstractResource implements WebResource 
{
 
     private String mimeType = null;
     private volatile String weakETag;
+    private volatile String strongETag;
 
 
     protected AbstractResource(WebResourceRoot root, String webAppPath) {
@@ -75,6 +81,52 @@ public abstract class AbstractResource implements 
WebResource {
         return weakETag;
     }
 
+    @Override
+    public final String getStrongETag() {
+        if (strongETag == null) {
+            synchronized (this) {
+                if (strongETag == null) {
+                    long contentLength = getContentLength();
+                    long lastModified = getLastModified();
+                    if (contentLength > 0 && lastModified > 0) {
+                        try (InputStream is = getInputStream()) {
+                            if (contentLength <= 16 * 1024) {
+                                byte[] buf = new byte[(int) contentLength];
+                                int n = IOTools.readFully(is, buf);
+                                if (n > 0) {
+                                    buf = 
ConcurrentMessageDigest.digest("SHA-1", buf);
+                                    strongETag = HexUtils.toHexString(buf);
+                                } else {
+                                    strongETag = getETag();
+                                }
+                            } else {
+                                byte[] buf = new byte[4096];
+                                try {
+                                    MessageDigest digest = 
MessageDigest.getInstance("SHA-1");
+                                    while (true) {
+                                        int n = is.read(buf);
+                                        if (n <= 0) {
+                                            break;
+                                        }
+                                        digest.update(buf, 0, n);
+                                    }
+                                    strongETag = 
HexUtils.toHexString(digest.digest());
+                                } catch (Exception e) {
+                                    strongETag = getETag();
+                                }
+                            }
+                        } catch (IOException e) {
+                            strongETag = getETag();
+                        }
+                    } else {
+                        strongETag = getETag();
+                    }
+                }
+            }
+        }
+        return strongETag;
+    }
+
     @Override
     public final void setMimeType(String mimeType) {
         this.mimeType = mimeType;
diff --git a/java/org/apache/catalina/webresources/CachedResource.java 
b/java/org/apache/catalina/webresources/CachedResource.java
index ef9236fc1a..74fa278d20 100644
--- a/java/org/apache/catalina/webresources/CachedResource.java
+++ b/java/org/apache/catalina/webresources/CachedResource.java
@@ -291,6 +291,11 @@ public class CachedResource implements WebResource {
         return webResource.getETag();
     }
 
+    @Override
+    public String getStrongETag() {
+        return webResource.getStrongETag();
+    }
+
     @Override
     public void setMimeType(String mimeType) {
         webResource.setMimeType(mimeType);
diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java 
b/test/org/apache/catalina/servlets/TestWebdavServlet.java
index af51e99ffa..1a9d28ac29 100644
--- a/test/org/apache/catalina/servlets/TestWebdavServlet.java
+++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java
@@ -361,6 +361,7 @@ public class TestWebdavServlet extends TomcatBaseTest {
         webdavServlet.addInitParameter("listings", "true");
         webdavServlet.addInitParameter("secret", "foo");
         webdavServlet.addInitParameter("readonly", "false");
+        webdavServlet.addInitParameter("useStrongETags", "true");
         ctxt.addServletMappingDecoded("/*", "webdav");
         tomcat.start();
 
@@ -607,6 +608,19 @@ public class TestWebdavServlet extends TomcatBaseTest {
         client.processRequest(true);
         Assert.assertEquals(HttpServletResponse.SC_CREATED, 
client.getStatusCode());
 
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 3000; i++) {
+            sb.append(CONTENT);
+        }
+        client.setRequest(new String[] { "PUT /file6.txt HTTP/1.1" + 
SimpleHttpClient.CRLF +
+                "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
+                "Content-Length: " + String.valueOf(sb.length()) +  
SimpleHttpClient.CRLF +
+                "Connection: Close" + SimpleHttpClient.CRLF +
+                SimpleHttpClient.CRLF + sb.toString() });
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_CREATED, 
client.getStatusCode());
+
         // Verify that everything created is there
         client.setRequest(new String[] { "PROPFIND / HTTP/1.1" + 
SimpleHttpClient.CRLF +
                 "Host: localhost:" + getPort() + SimpleHttpClient.CRLF +
@@ -618,6 +632,8 @@ public class TestWebdavServlet extends TomcatBaseTest {
         
Assert.assertFalse(client.getResponseBody().contains("/myfolder/file4.txt"));
         Assert.assertTrue(client.getResponseBody().contains("/file7.txt"));
         Assert.assertTrue(client.getResponseBody().contains("Second-"));
+        
Assert.assertTrue(client.getResponseBody().contains("d1dc021f456864e84f9a37b7a6f51c51301128a0"));
+        
Assert.assertTrue(client.getResponseBody().contains("f3390fe2e5546dac3d1968970df1a222a3a39c00"));
         String timeoutValue = 
client.getResponseBody().substring(client.getResponseBody().indexOf("Second-"));
         timeoutValue = timeoutValue.substring("Second-".length(), 
timeoutValue.indexOf('<'));
         Assert.assertTrue(Integer.valueOf(timeoutValue).intValue() <= 20);
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 87fee2d758..ebdf0f6a2d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -153,6 +153,12 @@
         Avoid quotes for numeric values in the JSON generated by the status
         servlet. (remm)
       </fix>
+      <add>
+        Add strong ETag support for the WebDAV and default servlet, which can
+        be enabled by using the <code>useStrongETags</code> init parameter with
+        a value set to <code>true</code>. The ETag generated will be a SHA-1
+        checksum of the resource content. (remm)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">


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

Reply via email to