This is an automated email from the ASF dual-hosted git repository.
remm pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 6047f4d947 Fix processing of the time-taken token
6047f4d947 is described below
commit 6047f4d9470111688003f5a0aab40459dda354da
Author: remm <[email protected]>
AuthorDate: Fri Mar 7 17:02:32 2025 +0100
Fix processing of the time-taken token
Simplify.
Add test case.
---
.../catalina/valves/ExtendedAccessLogValve.java | 18 +--
.../valves/TestExtendedAccessLogValve.java | 171 +++++++++++++++++----
...ve.java => TestExtendedAccessLogValveWrap.java} | 2 +-
webapps/docs/changelog.xml | 4 +
4 files changed, 149 insertions(+), 46 deletions(-)
diff --git a/java/org/apache/catalina/valves/ExtendedAccessLogValve.java
b/java/org/apache/catalina/valves/ExtendedAccessLogValve.java
index aebc853846..745ba56889 100644
--- a/java/org/apache/catalina/valves/ExtendedAccessLogValve.java
+++ b/java/org/apache/catalina/valves/ExtendedAccessLogValve.java
@@ -134,23 +134,7 @@ public class ExtendedAccessLogValve extends AccessLogValve
{
}
/* Wrap all values in double quotes. */
- StringBuilder buffer = new StringBuilder(svalue.length() + 2);
- buffer.append('\"');
- int i = 0;
- while (i < svalue.length()) {
- int j = svalue.indexOf('\"', i);
- if (j == -1) {
- buffer.append(svalue.substring(i));
- i = svalue.length();
- } else {
- buffer.append(svalue.substring(i, j + 1));
- buffer.append('"');
- i = j + 1;
- }
- }
-
- buffer.append('\"');
- return buffer.toString();
+ return "\"" + svalue.replace("\"", "\"\"") + "\"";
}
@Override
diff --git a/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
b/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
index fb3377ef3b..348807617d 100644
--- a/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
+++ b/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
@@ -16,48 +16,163 @@
*/
package org.apache.catalina.valves;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.junit.Assert;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
-public class TestExtendedAccessLogValve {
+import org.apache.catalina.Context;
+import org.apache.catalina.Host;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
- @Test
- public void alpha() {
- Assert.assertEquals("\"foo\"", ExtendedAccessLogValve.wrap("foo"));
- }
+@RunWith(Parameterized.class)
+public class TestExtendedAccessLogValve extends TomcatBaseTest {
- @Test
- public void testNull() {
- Assert.assertEquals("-", ExtendedAccessLogValve.wrap(null));
+ @Parameterized.Parameters(name = "{index}: pattern=[{0}]")
+ public static Collection<Object[]> data() {
+ List<Object[]> patterns = new ArrayList<>();
+ patterns.add(new Object[]{"basic", "time cs-method cs-uri-stem
cs-uri-query"});
+ patterns.add(new Object[]{"ip", "time cs-method sc-status c-ip s-ip
s-dns c-dns"});
+ patterns.add(new Object[]{"headers", "time cs-method cs(Referer)
cs(Cookie) sc(Content-Type)"});
+ patterns.add(new Object[]{"bytes", "date time cs-method cs-uri-stem
bytes time-taken cached"});
+ return patterns;
}
- @Test
- public void empty() {
- Assert.assertEquals("\"\"", ExtendedAccessLogValve.wrap(""));
- }
+ @Parameter(0)
+ public String name;
- @Test
- public void singleQuoteMiddle() {
- Assert.assertEquals("\"foo'bar\"",
ExtendedAccessLogValve.wrap("foo'bar"));
- }
+ @Parameter(1)
+ public String logPattern;
@Test
- public void doubleQuoteMiddle() {
- Assert.assertEquals("\"foo\"\"bar\"",
ExtendedAccessLogValve.wrap("foo\"bar"));
+ public void testLogFormat() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+ Host host = tomcat.getHost();
+
+ // Create temporary directory for logs
+ File logDir = getTemporaryDirectory();
+
+ ExtendedAccessLogValve valve = new ExtendedAccessLogValve();
+ valve.setPattern(logPattern);
+ valve.setDirectory(logDir.getAbsolutePath());
+ valve.setPrefix("access_log_" + name);
+
+ host.getPipeline().addValve(valve);
+
+ // Add test servlet
+ Context ctx = getProgrammaticRootContext();
+ Tomcat.addServlet(ctx, "testServlet", new HttpServlet() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse
resp) throws IOException {
+ resp.getWriter().write("Test response");
+ }
+ });
+ ctx.addServletMappingDecoded("/test", "testServlet");
+
+ tomcat.start();
+
+ String url = "http://localhost:" + getPort() + "/test";
+ ByteChunk out = new ByteChunk();
+ Map<String, List<String>> reqHead = new HashMap<>();
+ List<String> cookieHeaders = new ArrayList<>();
+ cookieHeaders.add("COOKIE-1_1=1_1;COOKIE-1_2=1_2;COOKIE-1_3=1_3");
+ reqHead.put("Cookie", cookieHeaders);
+ List<String> refererHeader = new ArrayList<>();
+ refererHeader.add("/some/path");
+ reqHead.put("Referer", refererHeader);
+ List<String> contentTypeHeader = new ArrayList<>();
+ contentTypeHeader.add("text/plain");
+ reqHead.put("Content-Type", contentTypeHeader);
+ Map<String, List<String>> resHead = new HashMap<>();
+ int status = getUrl(url, out, reqHead, resHead);
+ Assert.assertEquals(HttpServletResponse.SC_OK, status);
+
+ tomcat.stop();
+
+ // Read log file
+ File[] logFiles = logDir.listFiles((dir, fname) ->
fname.startsWith("access_log_" + name));
+ Assert.assertNotNull(logFiles);
+ Assert.assertTrue(logFiles.length > 0);
+
+ File logFile = logFiles[0];
+ String content = new String(Files.readAllBytes(logFile.toPath()),
StandardCharsets.UTF_8);
+
+ processLogContent(content);
+
}
- @Test
- public void doubleQuoteStart() {
- Assert.assertEquals("\"\"\"foobar\"",
ExtendedAccessLogValve.wrap("\"foobar"));
+ private void processLogContent(String content) {
+ String[] lines = content.split("\\r?\\n");
+
+ List<String> dataLines = new ArrayList<>();
+ for (String line : lines) {
+ if (!line.startsWith("#")) { // Skip directives
+ dataLines.add(line.trim());
+ } else {
+ if (line.startsWith("#Fields: ")) {
+ Assert.assertEquals(line.substring("#Fields: ".length()),
logPattern);
+ }
+ }
+ }
+
+ Assert.assertTrue("No data entries found", !dataLines.isEmpty());
+
+ String entryLine = dataLines.get(0);
+ String[] parts = entryLine.split("\\s+");
+
+ String[] expectedFields = logPattern.split("\\s+");
+
+ Assert.assertEquals(expectedFields.length, parts.length);
+
+ for (int i=0; i < expectedFields.length; i++) {
+ checkField(expectedFields[i], parts[i]);
+ }
}
- @Test
- public void doubleQuoteEnd() {
- Assert.assertEquals("\"foobar\"\"\"",
ExtendedAccessLogValve.wrap("foobar\""));
+ private void checkField(String fieldId, String value) {
+ if ("time".equals(fieldId)) {
+ Assert.assertTrue("Invalid time format", isTimeFormat(value));
+ } else if ("cs-method".equals(fieldId)) {
+ Assert.assertEquals("GET", value);
+ } else if (fieldId.startsWith("c-ip")) {
+ // IPv4 with optional port
+
Assert.assertTrue(value.matches("^\\d{1,3}(\\.\\d{1,3}){3}(:\\d+)?$"));
+ } else if ("cs-uri-stem".equals(fieldId)) {
+ Assert.assertEquals("/test", value);
+ } else if (fieldId.equals("sc-status")) {
+ Assert.assertEquals("200", value);
+ } else if (fieldId.contains("Referer")) {
+ Assert.assertTrue(value.equals("\"/some/path\""));
+ } else if ("bytes".equals(fieldId)) {
+ // Non-negative integer or '-'
+ Assert.assertTrue(value.equals("-") || value.matches("\\d+"));
+ } else if ("time-taken".equals(fieldId)) {
+ // Fixed format (e.g., 0.015)
+ Assert.assertTrue(value.matches("^\\d+(\\.\\d+)?$"));
+ }
}
- @Test
- public void doubleQuote() {
- Assert.assertEquals("\"\"\"\"", ExtendedAccessLogValve.wrap("\""));
+ private boolean isTimeFormat(String s) {
+ return Pattern.matches("^\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$", s);
}
-}
+}
\ No newline at end of file
diff --git a/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
b/test/org/apache/catalina/valves/TestExtendedAccessLogValveWrap.java
similarity index 97%
copy from test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
copy to test/org/apache/catalina/valves/TestExtendedAccessLogValveWrap.java
index fb3377ef3b..129f6a371e 100644
--- a/test/org/apache/catalina/valves/TestExtendedAccessLogValve.java
+++ b/test/org/apache/catalina/valves/TestExtendedAccessLogValveWrap.java
@@ -19,7 +19,7 @@ package org.apache.catalina.valves;
import org.junit.Assert;
import org.junit.Test;
-public class TestExtendedAccessLogValve {
+public class TestExtendedAccessLogValveWrap {
@Test
public void alpha() {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8e17241f3e..adbcf2dc3b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -118,6 +118,10 @@
rate limit based on the exact values configured. Based on pull request
<pr>794</pr> by Chenjp. (markt)
</add>
+ <fix>
+ Fix parsing of the <code>time-taken</code> token in the
+ <code>ExtendedAccessLogValve</code>. (remm)
+ </fix>
</changelog>
</subsection>
</section>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]