This is an automated email from the ASF dual-hosted git repository.

markt-asf 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 2d6f161554 Add support for '%%' to represent a literal '%' in the 
access log
2d6f161554 is described below

commit 2d6f161554783be1e9a0ced09b77814c46e58663
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 | 31 +++++++---
 .../apache/catalina/valves/TestAccessLogValve.java |  4 ++
 webapps/docs/changelog.xml                         | 68 +++-------------------
 webapps/docs/config/valve.xml                      |  1 +
 5 files changed, 38 insertions(+), 68 deletions(-)

diff --git a/java/org/apache/catalina/valves/AbstractAccessLogValve.java 
b/java/org/apache/catalina/valves/AbstractAccessLogValve.java
index 5519e0f57f..94432c6362 100644
--- a/java/org/apache/catalina/valves/AbstractAccessLogValve.java
+++ b/java/org/apache/catalina/valves/AbstractAccessLogValve.java
@@ -2040,6 +2040,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 d680ccf7f7..85f2ca6a79 100644
--- a/java/org/apache/catalina/valves/JsonAccessLogValve.java
+++ b/java/org/apache/catalina/valves/JsonAccessLogValve.java
@@ -150,6 +150,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();
@@ -161,10 +162,10 @@ public class JsonAccessLogValve extends AccessLogValve {
                 lit.remove();
                 continue;
             }
-            // Remove items which should be written as
-            // Json objects and add them later in correct order
             JsonWrappedElement wrappedLogElement = (JsonWrappedElement) 
logElement;
             AccessLogElement ale = wrappedLogElement.getDelegate();
+            // Remove items which should be written as
+            // Json objects and add them later in correct order
             if (ale instanceof HeaderElement) {
                 
subTypeLists.get(Character.valueOf('i')).add(wrappedLogElement);
                 lit.remove();
@@ -182,6 +183,7 @@ public class JsonAccessLogValve extends AccessLogValve {
                 lit.remove();
             } else {
                 // Keep the simple items and add separator
+                hasSimple = true;
                 lit.add(new CharElement(','));
             }
         }
@@ -191,11 +193,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('}'));
@@ -206,13 +211,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 a244bd5850..81691cd1c1 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 a42ccf1041..50fc37dfbd 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 9.0.119 (remm)" 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 9.0.118 (remm)" 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 4951591294..ea1745d64f 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]

Reply via email to