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
commit 70e10cd206e2209ab6109383bc3b9be97466968b Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Nov 2 09:56:56 2026 +0100 WW-4164 fix(core): match the full requested name when slashes are allowed With slashes in action names the raw name spans the whole path after the namespace, so the declaration probe rebuilds the URI from the probe's namespace and name instead of comparing only the last segment. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../dispatcher/mapper/DefaultActionMapper.java | 12 ++++++++---- .../dispatcher/mapper/DefaultActionMapperTest.java | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java index 63208956f..c4ddf8575 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java @@ -511,7 +511,7 @@ public class DefaultActionMapper implements ActionMapper { private boolean declaresExtensions(String candidate, RuntimeConfiguration runtimeConfiguration, ConfigurationManager configManager) { ActionMapping probe = new ActionMapping(); parseNameAndNamespace(candidate, probe, configManager); - if (!keepsRequestedName(candidate, probe.getName())) { + if (!keepsRequestedName(candidate, probe)) { return false; } parseActionName(probe); @@ -519,9 +519,13 @@ public class DefaultActionMapper implements ActionMapper { return config != null && !config.getExtensions().isEmpty(); } - private static boolean keepsRequestedName(String uri, String name) { - String lastSegment = uri.substring(uri.lastIndexOf('/') + 1); - return name.equals(lastSegment) || name.endsWith("/" + lastSegment); + private boolean keepsRequestedName(String uri, ActionMapping probe) { + String name = probe.getName(); + if (!allowSlashesInActionNames) { + return uri.substring(uri.lastIndexOf('/') + 1).equals(name); + } + String namespace = "/".equals(probe.getNamespace()) ? "" : probe.getNamespace(); + return uri.equals(name) || uri.equals(namespace + "/" + name); } private DeclaredExtensions declaredExtensions(RuntimeConfiguration runtimeConfiguration) { diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java index 7113f60d8..cee45edc0 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java @@ -1153,6 +1153,25 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase { assertThat(mapDeclared(declaredMapper(), "/manual(1).html")).isNull(); } + public void testDeclaringDefaultActionDoesNotClaimDisallowedNameWithSlashes() { + loadConfigurationProviders(new StrutsXmlConfigurationProvider(EXTENSION_DEFAULT_ACTION_CONFIG)); + DefaultActionMapper mapper = declaredMapper(); + mapper.setSlashesInActionNames("true"); + + assertThat(mapDeclared(mapper, "/x(1)/index.html")).isNull(); + } + + public void testDeclaringDefaultActionClaimsItsOwnNameWithSlashes() { + loadConfigurationProviders(new StrutsXmlConfigurationProvider(EXTENSION_DEFAULT_ACTION_CONFIG)); + DefaultActionMapper mapper = declaredMapper(); + mapper.setSlashesInActionNames("true"); + + ActionMapping mapping = mapDeclared(mapper, "/index.html"); + + assertThat(mapping.getName()).isEqualTo("index"); + assertThat(mapping.getExtension()).isEqualTo("html"); + } + public void testDeclaredExtensionClaimedForLiteralBangNameWithoutDmi() { loadConfigurationProviders(new StrutsXmlConfigurationProvider(EXTENSION_BANG_CONFIG));
