On 08/10/2026 16:42, [email protected] wrote:
This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 363ec9f0d3c8a9e8518c05473ababc419efae608
Author: opencode <[email protected]>
AuthorDate: Thu Oct 8 14:46:53 2026 +0200
Keep the Last-Modified header for large unbuffered SSI documents
In the default unbuffered mode, SSIServlet streams the processed output
directly to the response writer. Documents larger than the response
buffer commit the response while processing, so the Last-Modified header
that was set after processing completed was silently ignored and large
SSI pages were served without it.
Set the header from the source resource's last modified time before the
output is generated, and upgrade it to the value aggregated from the
directives afterwards only when it is newer and the response has not
been committed yet. This keeps the previous behavior for buffered
processing and responses that stay uncommitted.
I think this needs to be reverted.
For large responses, an incorrect, source based, Last-Modified date will
be set which is worse than not setting one at all.
Mark
---
java/org/apache/catalina/ssi/SSIServlet.java | 11 ++++--
test/org/apache/catalina/ssi/TestSsiServlet.java | 45 ++++++++++++++++++++++++
2 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/java/org/apache/catalina/ssi/SSIServlet.java
b/java/org/apache/catalina/ssi/SSIServlet.java
index 2a2fe53dea..8281689b87 100644
--- a/java/org/apache/catalina/ssi/SSIServlet.java
+++ b/java/org/apache/catalina/ssi/SSIServlet.java
@@ -211,8 +211,15 @@ public class SSIServlet extends HttpServlet {
}
try (BufferedReader bufferedReader = new BufferedReader(isr)) {
- long lastModified = ssiProcessor.process(bufferedReader,
resourceInfo.getLastModified(), printWriter);
- if (lastModified > 0) {
+ // Set the header from the source before the output is generated.
In the
+ // unbuffered mode a response larger than the response buffer is
committed
+ // while processing, after which setting the header is silently
ignored.
+ long sourceLastModified = resourceInfo.getLastModified();
+ if (sourceLastModified > 0) {
+ res.setDateHeader("last-modified", sourceLastModified);
+ }
+ long lastModified = ssiProcessor.process(bufferedReader,
sourceLastModified, printWriter);
+ if (lastModified > sourceLastModified && !res.isCommitted()) {
res.setDateHeader("last-modified", lastModified);
}
if (stringWriter != null) {
diff --git a/test/org/apache/catalina/ssi/TestSsiServlet.java
b/test/org/apache/catalina/ssi/TestSsiServlet.java
index 28e8fa6337..d1363782c3 100644
--- a/test/org/apache/catalina/ssi/TestSsiServlet.java
+++ b/test/org/apache/catalina/ssi/TestSsiServlet.java
@@ -17,6 +17,9 @@
package org.apache.catalina.ssi;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
@@ -72,4 +75,46 @@ public class TestSsiServlet extends TomcatBaseTest {
Assert.assertFalse(body.contains("(none)"));
}
+
+
+ @Test
+ public void testLargeDocumentLastModifiedHeader() throws Exception {
+ // A document larger than the response buffer commits the unbuffered
+ // response while the output is being generated. The Last-Modified
+ // header must be set before that happens.
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File(getTemporaryDirectory(), "ssi-large");
+ Assert.assertTrue(appDir.mkdirs() || appDir.isDirectory());
+ addDeleteOnTearDown(appDir);
+ File doc = new File(appDir, "large.shtml");
+ try (Writer writer = new OutputStreamWriter(new FileOutputStream(doc),
StandardCharsets.ISO_8859_1)) {
+ writer.write("X".repeat(20000));
+ writer.write("<!--#flastmod file=\"large.shtml\" -->TAIL");
+ }
+
+ Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+ Tomcat.addServlet(ctxt, "ssi", new SSIServlet());
+ ctxt.addServletMapping("*.shtml", "ssi");
+
+ tomcat.start();
+
+ Map<String,List<String>> resHeaders = new HashMap<>();
+ String path = "http://localhost:" + getPort() + "/large.shtml";
+ ByteChunk out = new ByteChunk();
+
+ int rc = getUrl(path, out, resHeaders);
+ Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+ String body = new String(out.getBuffer(), 0, out.getLength(),
StandardCharsets.ISO_8859_1);
+ Assert.assertTrue(body.endsWith("TAIL"));
+ // Headers are case-insensitive. SSIServlet uses the lower-case name.
+ String lastModified = null;
+ for (Map.Entry<String,List<String>> header : resHeaders.entrySet()) {
+ if ("Last-Modified".equalsIgnoreCase(header.getKey())) {
+ Assert.assertEquals(1, header.getValue().size());
+ lastModified = header.getValue().get(0);
+ }
+ }
+ Assert.assertNotNull(lastModified);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]