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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]