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

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/main by this push:
     new 2620abedd WW-5706 fix(core): align RestfulActionMapper action name 
handling with DefaultActionMapper (#1880)
2620abedd is described below

commit 2620abedd86eb79bd1d25550bfcf878ba7ace4a9
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Aug 31 20:04:15 2026 +0200

    WW-5706 fix(core): align RestfulActionMapper action name handling with 
DefaultActionMapper (#1880)
    
    RestfulActionMapper derived the action name straight from the request
    URI, unlike DefaultActionMapper which validates it via cleanupActionName
    against the allowedActionNames pattern. Apply the same check (and the
    struts.allowed.action.names / struts.default.action.name settings) so
    both mappers handle action names consistently.
    
    Fixes: https://issues.apache.org/jira/browse/WW-5706
    
    
    Claude-Session: https://claude.ai/code/session_016XMyQ1CRuYZkqygmD4aGHv
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../dispatcher/mapper/RestfulActionMapper.java     | 40 +++++++++++++++++++++-
 .../dispatcher/mapper/RestfulActionMapperTest.java | 16 +++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java
index d93d45e98..92307d691 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java
@@ -24,11 +24,13 @@ import jakarta.servlet.http.HttpServletRequest;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.struts2.RequestUtils;
+import org.apache.struts2.StrutsConstants;
 import org.apache.struts2.url.UrlDecoder;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.StringTokenizer;
+import java.util.regex.Pattern;
 
 /**
  * Simple Restfull Action Mapper to support REST application
@@ -41,11 +43,31 @@ public class RestfulActionMapper implements ActionMapper {
 
     private UrlDecoder decoder;
 
+    /**
+     * Matches action names allowed in the request URI, aligned with {@link 
DefaultActionMapper}.
+     */
+    private Pattern allowedActionNames = 
Pattern.compile("[a-zA-Z0-9._!/\\-]*");
+
+    /**
+     * Action name used when the name extracted from the URI is not allowed, 
aligned with {@link DefaultActionMapper}.
+     */
+    private String defaultActionName = "index";
+
     @Inject
     public void setDecoder(UrlDecoder decoder) {
         this.decoder = decoder;
     }
 
+    @Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = 
false)
+    public void setAllowedActionNames(String allowedActionNames) {
+        this.allowedActionNames = Pattern.compile(allowedActionNames);
+    }
+
+    @Inject(value = StrutsConstants.STRUTS_DEFAULT_ACTION_NAME, required = 
false)
+    public void setDefaultActionName(String defaultActionName) {
+        this.defaultActionName = defaultActionName;
+    }
+
     /* (non-Javadoc)
      * @see 
org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(jakarta.servlet.http.HttpServletRequest)
      */
@@ -57,7 +79,7 @@ public class RestfulActionMapper implements ActionMapper {
             return null;
         }
 
-        String actionName = uri.substring(1, nextSlash);
+        String actionName = cleanupActionName(uri.substring(1, nextSlash));
         Map<String, Object> parameters = new HashMap<>();
         try {
             StringTokenizer st = new StringTokenizer(uri.substring(nextSlash), 
"/");
@@ -96,6 +118,22 @@ public class RestfulActionMapper implements ActionMapper {
         return new ActionMapping(actionName, null, null, null);
     }
 
+    /**
+     * Checks action name against the allowed pattern; if it does not match, 
returns the default action name.
+     * Mirrors {@link DefaultActionMapper#cleanupActionName(String)}.
+     *
+     * @param rawActionName action name extracted from the URI
+     * @return safe action name
+     */
+    protected String cleanupActionName(final String rawActionName) {
+        if (allowedActionNames.matcher(rawActionName).matches()) {
+            return rawActionName;
+        } else {
+            LOG.warn("{} did not match allowed action names {} - default 
action {} will be used!", rawActionName, allowedActionNames, defaultActionName);
+            return defaultActionName;
+        }
+    }
+
     /* (non-Javadoc)
      * @see 
org.apache.struts2.dispatcher.mapper.ActionMapper#getUriFromActionMapping(org.apache.struts2.dispatcher.mapper.ActionMapping)
      */
diff --git 
a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java
 
b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java
index d8bc9d553..79efccfa0 100644
--- 
a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java
@@ -105,6 +105,22 @@ public class RestfulActionMapperTest extends 
StrutsInternalTestCase {
         assertEquals("europe", am.getParams().get("region"));
     }
 
+    public void testGetMappingRejectsActionNameWithDisallowedCharacters() {
+        StrutsMockHttpServletRequest request = new 
StrutsMockHttpServletRequest();
+        request.setServletPath("/%{1+1}/x");
+
+        ActionMapping am = mapper.getMapping(request, null);
+        assertEquals("index", am.getName());
+    }
+
+    public void testGetMappingAcceptsRegularActionName() {
+        StrutsMockHttpServletRequest request = new 
StrutsMockHttpServletRequest();
+        request.setServletPath("/my-app.action/x");
+
+        ActionMapping am = mapper.getMapping(request, null);
+        assertEquals("my-app.action", am.getName());
+    }
+
     protected void setUp() throws Exception {
         super.setUp();
         mapper = new RestfulActionMapper();

Reply via email to