[ 
https://issues.apache.org/jira/browse/WW-4164?focusedWorklogId=1051812&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1051812
 ]

ASF GitHub Bot logged work on WW-4164:
--------------------------------------

                Author: ASF GitHub Bot
            Created on: 02/Nov/26 07:55
            Start Date: 02/Nov/26 07:55
    Worklog Time Spent: 10m 
      Work Description: lukaszlenart opened a new pull request, #2093:
URL: https://github.com/apache/struts/pull/2093

   Fixes [WW-4164](https://issues.apache.org/jira/browse/WW-4164)
   
   Also resolves the design question moved here from 
[WW-3193](https://issues.apache.org/jira/browse/WW-3193) (form action 
inheriting the current page's extension).
   
   ## What
   
   `<package>` and `<action>` accept an `extension` attribute 
(`struts-7.5.dtd`) with the same format as `struts.action.extension`: 
comma-separated, ordered, first entry is the default, an empty entry means "no 
extension". Resolution: action → package → parent packages → global constant.
   
   - **Inbound** — `DefaultActionMapper` strips an extension declared only by 
packages/actions when the stripped name resolves (exact or wildcard match, 
never through `default-action-ref`) to an action that declares an extension 
list. Everything else maps exactly as today, so static files and undeclared 
actions are untouched.
   - **Enforcement** — new `actionExtension` interceptor 
(`ActionExtensionInterceptor`), placed after `httpMethod` in `basicStack`, 
`paramsPrepareParamsStack` and `defaultStack`. A disallowed extension returns 
the `bad-extension` result when the action can resolve one (configurable via 
`badExtensionResultName`), otherwise 404. Only the URL's own action is checked 
(`chain` / `<s:action>` targets are not); `action:`-prefix buttons are checked 
against the button's target.
   - **Outbound** — `ActionExtensionResolver` sets the target action's 
extension for `s:url`/`s:a`, `s:form`, `redirectAction` and `postback`: keep 
the current extension when the target allows it, otherwise its first entry. It 
is a no-op when nothing in the configuration declares extensions.
   - Startup WARNs when an action declares extensions but its stack lacks the 
interceptor, and when a non-default `ActionMapper` is configured.
   
   Design: 
`docs/superpowers/specs/2026-11-01-WW-4164-per-action-extensions-design.md`. 
This is URL canonicalisation, not access control.
   
   ## Compatibility
   
   No behaviour change for applications without an `extension` attribute 
(pinned by `ActionExtensionEndToEndTest` and the existing suites).
   
   ## 7.5.0 Version Notes / Migration Guide lines
   
   - New `extension` attribute on `<package>` and `<action>` (struts-7.5.dtd).
   - New `actionExtension` interceptor in `basicStack`, 
`paramsPrepareParamsStack` and `defaultStack`; custom stacks need it to enforce 
declared extensions.
   - `Component`, `ServletActionRedirectResult` and `PostbackResult` now 
require an injected `ActionExtensionResolver`; tests that build them by hand 
need `container.inject(...)` or `setActionExtensionResolver(...)`.
   - New `RuntimeConfiguration.getMatchedActionConfig(namespace, name)` 
(default method: exact/wildcard match without the `default-action-ref` 
fallback); custom implementations should override it.
   - A link to a name served only by a `default-action-ref` that declares 
extensions is built with the global extension and is rejected by that action — 
link to the action's real name.
   - Convention/annotation support is a follow-up ticket.
   
   ## struts-site (separate PR)
   
   "Extensions" section in `core-developers/action-configuration.md`, next to 
"HTTP Methods":
   
   > Since 7.5.0 a package or an action can declare which extensions it answers 
on with the `extension` attribute, using the same format as 
`struts.action.extension` (`extension="html"`, `extension="json,"`, 
`extension=""` for no extension). An action inherits the list from its package 
and parent packages; without one, the global `struts.action.extension` applies. 
Requests with another extension are rejected by the `actionExtension` 
interceptor with a `bad-extension` result, or 404 when no such result is 
mapped. URLs generated by `<s:url>`, `<s:a>`, `<s:form>`, `redirectAction` and 
`postback` use the target action's list. Chained actions and `<s:action>` are 
not checked, and mappers other than `DefaultActionMapper` may not honour the 
attribute. The attribute canonicalises URLs; it is not an access-control 
mechanism.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   




Issue Time Tracking
-------------------

            Worklog Id:     (was: 1051812)
    Remaining Estimate: 0h
            Time Spent: 10m

> Improve support for multiple extensions
> ---------------------------------------
>
>                 Key: WW-4164
>                 URL: https://issues.apache.org/jira/browse/WW-4164
>             Project: Struts 2
>          Issue Type: Improvement
>          Components: XML Configuration
>    Affects Versions: 2.3.15.1
>            Reporter: Jasper Rosenberg
>            Assignee: Lukasz Lenart
>            Priority: Major
>             Fix For: 7.5.0
>
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> Currently if you support multiple extensions (eg. 
> struts.action.extension=action,html,,) there is no way to tell struts for a 
> particular action which extension you always want to use.  This has two 
> downsides:
> 1. When struts has to generate an url, for example in the form tag, or the 
> "redirectAction" result type, there is no way to specify the extension to 
> use, so it ends up using the current invocation's extension if possible, or 
> otherwise the default, neither of which might be correct.
> 2. You can invoke the action with any of the supported extensions, creating 
> duplicate pages with no clear canonical version.  (Bad for SEO as well)
> What I propose is allowing the user to specify an optional specific extension 
> at the package and action level in the struts xml configuration like so:
> {code:xml}
>   <package name="test" namespace="/" extends="default" extension="action">
>     <action name="a1" extension="">
>       <result>a1.ftl</result>
>     </action>
>     <action name="b1" extension="html">
>       <result>b1.ftl</result>
>     </action>
>     <action name="c1">
>       <result>b1.ftl</result>
>     </action>
>   </package>
> {code}
> 1. When selecting an extension when building an URL for an action, it would 
> first see if there was an extension to use at the action level.  If not it 
> would check the package level (including package inheritance).  If still no 
> specific extension, it would behave as it currently does.
> 2. When mapping an incoming URL to an action, the reverse will also hold.  If 
> the action or its package specify a specific extension, then the action can 
> only be matched if the URL has that extension.
> So in the above example, the following urls would be expected to work:
> 1. /a1 (action level no extension)
> 2. /b1.html (action level "html" extension)
> 3. /c1.action (no action level extension, using package level "action" 
> extension)
> However, an url like "/b1.action" will not match any actions because "action" 
> extension doesn't match the action's "html" extension.
> This change would be 100% backwards because if you don't specify any 
> extension attributes on the package or action, it falls back to the current 
> behavior.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to