This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 190019de66 Add support for '%%' to represent a literal '%' in the
access log
190019de66 is described below
commit 190019de6615ca462f375d40c4c782aa3d583dac
Author: Mark Thomas <[email protected]>
AuthorDate: Wed May 6 10:23:13 2026 +0100
Add support for '%%' to represent a literal '%' in the access log
Also fix various bugs with literal handling found in JSONAccessLogValve
while writing tests for literal '%' support.
Based on #1002 by Fabian Hahn.
---
.../catalina/valves/AbstractAccessLogValve.java | 2 +
.../apache/catalina/valves/JsonAccessLogValve.java | 29 ++++++---
.../apache/catalina/valves/TestAccessLogValve.java | 4 ++
webapps/docs/changelog.xml | 68 +++-------------------
webapps/docs/config/valve.xml | 1 +
5 files changed, 37 insertions(+), 67 deletions(-)
diff --git a/java/org/apache/catalina/valves/AbstractAccessLogValve.java
b/java/org/apache/catalina/valves/AbstractAccessLogValve.java
index 360c1ebbd9..ffd3e7c2ff 100644
--- a/java/org/apache/catalina/valves/AbstractAccessLogValve.java
+++ b/java/org/apache/catalina/valves/AbstractAccessLogValve.java
@@ -2199,6 +2199,8 @@ public abstract class AbstractAccessLogValve extends
ValveBase implements Access
*/
protected AccessLogElement createAccessLogElement(char pattern) {
switch (pattern) {
+ case '%':
+ return new StringElement("%");
case 'a':
return new RemoteAddrElement();
case 'A':
diff --git a/java/org/apache/catalina/valves/JsonAccessLogValve.java
b/java/org/apache/catalina/valves/JsonAccessLogValve.java
index 9932d81a60..1750b005c1 100644
--- a/java/org/apache/catalina/valves/JsonAccessLogValve.java
+++ b/java/org/apache/catalina/valves/JsonAccessLogValve.java
@@ -151,6 +151,7 @@ public class JsonAccessLogValve extends AccessLogValve {
for (Character pattern : SUB_OBJECT_PATTERNS.keySet()) {
subTypeLists.put(pattern, new ArrayList<>());
}
+ boolean hasSimple = false;
boolean hasSub = false;
List<AccessLogElement> logElements = new
ArrayList<>(Arrays.asList(super.createLogElements()));
ListIterator<AccessLogElement> lit = logElements.listIterator();
@@ -162,9 +163,9 @@ public class JsonAccessLogValve extends AccessLogValve {
lit.remove();
continue;
}
+ AccessLogElement ale = wrappedLogElement.getDelegate();
// Remove items which should be written as
// Json objects and add them later in correct order
- AccessLogElement ale = wrappedLogElement.getDelegate();
if (ale instanceof HeaderElement) {
subTypeLists.get(Character.valueOf('i')).add(wrappedLogElement);
lit.remove();
@@ -185,6 +186,7 @@ public class JsonAccessLogValve extends AccessLogValve {
lit.remove();
} else {
// Keep the simple items and add separator
+ hasSimple = true;
lit.add(new CharElement(','));
}
}
@@ -194,11 +196,14 @@ public class JsonAccessLogValve extends AccessLogValve {
hasSub = true;
}
}
- // remove last comma (or possibly "},")
- lit.previous();
- lit.remove();
- // Last item was a sub object, close it
+ if (hasSimple || hasSub) {
+ // remove last comma (or possibly "},")
+ lit.previous();
+ lit.remove();
+ }
+ // Add closing }
if (hasSub) {
+ // Last item was a sub object, close it as well
lit.add(new StringElement("}}"));
} else {
lit.add(new CharElement('}'));
@@ -209,13 +214,23 @@ public class JsonAccessLogValve extends AccessLogValve {
@Override
protected AccessLogElement createAccessLogElement(String name, char
pattern) {
AccessLogElement ale = super.createAccessLogElement(name, pattern);
- return new JsonWrappedElement(pattern, name, true, ale);
+ if ('%' == pattern) {
+ // Uses a pattern but is literal text so no need to wrap.
+ return ale;
+ } else {
+ return new JsonWrappedElement(pattern, name, true, ale);
+ }
}
@Override
protected AccessLogElement createAccessLogElement(char pattern) {
AccessLogElement ale = super.createAccessLogElement(pattern);
- return new JsonWrappedElement(pattern, true, ale);
+ if ('%' == pattern) {
+ // Uses a pattern but is literal text so no need to wrap.
+ return ale;
+ } else {
+ return new JsonWrappedElement(pattern, true, ale);
+ }
}
private static class JsonWrappedElement implements AccessLogElement,
CachedElement {
diff --git a/test/org/apache/catalina/valves/TestAccessLogValve.java
b/test/org/apache/catalina/valves/TestAccessLogValve.java
index 49784e6421..f6b4ed5d95 100644
--- a/test/org/apache/catalina/valves/TestAccessLogValve.java
+++ b/test/org/apache/catalina/valves/TestAccessLogValve.java
@@ -90,6 +90,10 @@ public class TestAccessLogValve extends TomcatBaseTest {
public static Collection<Object[]> parameters() {
List<Object[]> parameterSets = new ArrayList<>();
+ parameterSets.add(new Object[] {"literal", TEXT_TYPE, "/", "abc",
"abc"});
+ parameterSets.add(new Object[] {"literal", JSON_TYPE, "/", "abc",
"\\{\\}"});
+ parameterSets.add(new Object[] {"pct-pct", TEXT_TYPE, "/", "%%", "%"});
+ parameterSets.add(new Object[] {"pct-pct", JSON_TYPE, "/", "%%",
"\\{\\}"});
parameterSets.add(new Object[] {"pct-a", TEXT_TYPE, "/", "%a",
LOCAL_IP_PATTERN});
parameterSets.add(new Object[] {"pct-a", JSON_TYPE, "/", "%a",
"\\{\"remoteAddr\":\"" + LOCAL_IP_PATTERN + "\"\\}"});
parameterSets.add(new Object[] {"pct-A", TEXT_TYPE, "/", "%A",
IP_PATTERN});
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index bd8a956041..356b140ab6 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -108,6 +108,14 @@
issues do not "pop up" wrt. others).
-->
<section name="Tomcat 11.0.23 (markt)" rtext="in development">
+ <subsection name="Catalina">
+ <changelog>
+ <add>
+ Add support for literal <code>'%'</code> characters in access log
+ output. Based on pull request <pr>1002</pr> by Fabian Hahn. (markt)
+ </add>
+ </changelog>
+ </subsection>
</section>
<section name="Tomcat 11.0.22 (markt)" rtext="release in progress">
<subsection name="Catalina">
@@ -118,66 +126,6 @@
implementations), along with version compatibility warnings and
third-party library version information. (csutherl)
</add>
- <scode>
- Refactor generation of the remote user element in the access log to
- remove unnecessary code. (markt)
- </scode>
- <fix>
- Fix a regression in the previous release that meant <code>?-</code>
- could appear in the access log rather than <code>?</code> when the
query
- string was present but empty. (markt)
- </fix>
- <fix>
- Failed precondition should make WebDAV DELETE fail. <pr>982</pr>
- submitted by Mahmoud Alarby. (remm)
- </fix>
- <fix>
- Align the escaping in <code>ExtendedAccessLogValve</code> with the
other
- <code>AccessLogValve</code> implementations. (markt)
- </fix>
- <fix>
- <bug>70000</bug>: fix duplication of special headers in the response
- after commit, following fix for <bug>69967</bug>. (remm)
- </fix>
- <fix>
- Correct the handling of URIs mapped to a security constraint that only
- specifies the special <code>**</code> role for all authenticated users.
- Requests without authentication were receiving 403 responses rather
than
- 401 responses. (markt)
- </fix>
- <fix>
- Fix a race condition in
<code>StandardContext.getServletContext()</code>
- that could cause the <code>jakarta.servlet.context.tempdir</code>
- attribute to be lost during a context reload. Make the
- <code>context</code> field volatile and use locking to
- ensure only one <code>ApplicationContext</code> instance is created.
- (dsoumis)
- </fix>
- <fix>
- Update the Windows authentication (kerberos) documentation to reflect
- that both Java and Windows are removing / have removed support for
- RC4-HMAC. The guide now uses AES256-SHA1. (markt)
- </fix>
- <fix>
- Add a new initialisation parameter for WebDAV,
- <code>maxRequestBodySize</code> which limits the size of a WebDAV
- request body for LOCK and PROPFIND. The default value is 4096 bytes.
- (markt)
- </fix>
- <add>
- Add a new <code>caseSensitive</code> attribute to the
- <code>LockOutRealm</code> that controls the manner in which user names
- are treated when making locking decisions. The default is
- <code>false</code>, meaning user names are treated in a case
insensitive
- manner. (markt)
- </add>
- <fix>
- Correct the handling of invalid users with DIGEST authentication.
(markt)
- </fix>
- <fix>
- Ensure <code>RealmBase</code> finds all matching extension based
- security constraints. (markt)
- </fix>
</changelog>
</subsection>
<subsection name="Coyote">
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index fb19da1ce8..6ad322e86c 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -282,6 +282,7 @@
the current request and response. The following pattern codes are
supported:</p>
<ul>
+ <li><b><code>%%</code></b> - Literal '%' character</li>
<li><b><code>%a</code></b> - Remote IP address.
See also <code>%{xxx}a</code> below.</li>
<li><b><code>%A</code></b> - Local IP address</li>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]