This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch release/struts-6-8-x
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/release/struts-6-8-x by this
push:
new df97ee23d fix(i18n): WW-5549 validate locale parameters against
supportedLocale (#1602)
df97ee23d is described below
commit df97ee23dc05ff91a1d5678669b3a6f0f68df432
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Mar 6 07:50:38 2026 +0100
fix(i18n): WW-5549 validate locale parameters against supportedLocale
(#1602)
When supportedLocale is configured on I18nInterceptor, request_locale
and request_cookie_locale parameters were ignored because
AcceptLanguageLocaleHandler.find() matched the Accept-Language header
before session/cookie handlers checked their explicit locale parameters.
Additionally, stored locales (session/cookie) were never validated
against supportedLocale.
Changes:
- Add isLocaleSupported() helper to validate locales against config
- RequestLocaleHandler.find() now validates against supportedLocale
- AcceptLanguageLocaleHandler.find() checks request_only_locale first,
then falls back to Accept-Language matching
- SessionLocaleHandler.find() checks request_locale before super.find()
- CookieLocaleHandler.find() checks request_cookie_locale before
super.find()
- SessionLocaleHandler.read() discards stale session locales
- CookieLocaleHandler.read() discards stale cookie locales
Port of PR #1594 (bug fix only, no refactoring)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <[email protected]>
---
.../struts2/interceptor/I18nInterceptor.java | 75 +++++++++++++-------
.../struts2/interceptor/I18nInterceptorTest.java | 80 +++++++++++++++++++---
2 files changed, 120 insertions(+), 35 deletions(-)
diff --git
a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
index 6bce04244..e2aedba1d 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
@@ -65,7 +65,7 @@ public class I18nInterceptor extends AbstractInterceptor {
private Set<Locale> supportedLocale = Collections.emptySet();
- protected enum Storage { COOKIE, SESSION, REQUEST, ACCEPT_LANGUAGE }
+ protected enum Storage {COOKIE, SESSION, REQUEST, ACCEPT_LANGUAGE}
public void setParameterName(String parameterName) {
this.parameterName = parameterName;
@@ -103,10 +103,14 @@ public class I18nInterceptor extends AbstractInterceptor {
*/
public void setSupportedLocale(String supportedLocale) {
this.supportedLocale = TextParseUtil
- .commaDelimitedStringToSet(supportedLocale)
- .stream()
- .map(Locale::new)
- .collect(Collectors.toSet());
+ .commaDelimitedStringToSet(supportedLocale)
+ .stream()
+ .map(Locale::new)
+ .collect(Collectors.toSet());
+ }
+
+ protected boolean isLocaleSupported(Locale locale) {
+ return supportedLocale.isEmpty() || supportedLocale.contains(locale);
}
@Inject
@@ -222,8 +226,11 @@ public class I18nInterceptor extends AbstractInterceptor {
*/
protected interface LocaleHandler {
Locale find();
+
Locale read(ActionInvocation invocation);
+
Locale store(ActionInvocation invocation, Locale locale);
+
boolean shouldStore();
}
@@ -241,7 +248,10 @@ public class I18nInterceptor extends AbstractInterceptor {
Parameter requestedLocale = findLocaleParameter(actionInvocation,
requestOnlyParameterName);
if (requestedLocale.isDefined()) {
- return getLocaleFromParam(requestedLocale.getValue());
+ Locale locale = getLocaleFromParam(requestedLocale.getValue());
+ if (locale != null && isLocaleSupported(locale)) {
+ return locale;
+ }
}
return null;
@@ -278,6 +288,11 @@ public class I18nInterceptor extends AbstractInterceptor {
@Override
@SuppressWarnings("rawtypes")
public Locale find() {
+ Locale requestOnlyLocale = super.find();
+ if (requestOnlyLocale != null) {
+ return requestOnlyLocale;
+ }
+
if (!supportedLocale.isEmpty()) {
Enumeration locales =
actionInvocation.getInvocationContext().getServletRequest().getLocales();
while (locales.hasMoreElements()) {
@@ -287,7 +302,7 @@ public class I18nInterceptor extends AbstractInterceptor {
}
}
}
- return super.find();
+ return null;
}
}
@@ -300,20 +315,20 @@ public class I18nInterceptor extends AbstractInterceptor {
@Override
public Locale find() {
- Locale requestOnlyLocale = super.find();
+ Parameter requestedLocale = findLocaleParameter(actionInvocation,
parameterName);
+ if (requestedLocale.isDefined()) {
+ Locale locale = getLocaleFromParam(requestedLocale.getValue());
+ if (locale != null && isLocaleSupported(locale)) {
+ return locale;
+ }
+ }
+ Locale requestOnlyLocale = super.find();
if (requestOnlyLocale != null) {
- LOG.debug("Found locale under request only param, it won't be
stored in session!");
shouldStore = false;
return requestOnlyLocale;
}
- LOG.debug("Searching locale in request under parameter {}",
parameterName);
- Parameter requestedLocale = findLocaleParameter(actionInvocation,
parameterName);
- if (requestedLocale.isDefined()) {
- return getLocaleFromParam(requestedLocale.getValue());
- }
-
return null;
}
@@ -344,7 +359,12 @@ public class I18nInterceptor extends AbstractInterceptor {
Object sessionLocale =
invocation.getInvocationContext().getSession().get(attributeName);
if (sessionLocale instanceof Locale) {
locale = (Locale) sessionLocale;
- LOG.debug("Applied session locale: {}", locale);
+ if (!isLocaleSupported(locale)) {
+ LOG.debug("Stored session locale {} is not
supported, discarding", locale);
+ locale = null;
+ } else {
+ LOG.debug("Applied session locale: {}", locale);
+ }
}
}
}
@@ -368,17 +388,18 @@ public class I18nInterceptor extends AbstractInterceptor {
@Override
public Locale find() {
- Locale requestOnlySessionLocale = super.find();
-
- if (requestOnlySessionLocale != null) {
- shouldStore = false;
- return requestOnlySessionLocale;
- }
-
- LOG.debug("Searching locale in request under parameter {}",
requestCookieParameterName);
Parameter requestedLocale = findLocaleParameter(actionInvocation,
requestCookieParameterName);
if (requestedLocale.isDefined()) {
- return getLocaleFromParam(requestedLocale.getValue());
+ Locale locale = getLocaleFromParam(requestedLocale.getValue());
+ if (locale != null && isLocaleSupported(locale)) {
+ return locale;
+ }
+ }
+
+ Locale requestOnlyLocale = super.find();
+ if (requestOnlyLocale != null) {
+ shouldStore = false;
+ return requestOnlyLocale;
}
return null;
@@ -404,6 +425,10 @@ public class I18nInterceptor extends AbstractInterceptor {
for (Cookie cookie : cookies) {
if (attributeName.equals(cookie.getName())) {
locale = getLocaleFromParam(cookie.getValue());
+ if (locale != null && !isLocaleSupported(locale)) {
+ LOG.debug("Stored cookie locale {} is not
supported, discarding", locale);
+ locale = null;
+ }
}
}
}
diff --git
a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
index 604d61be6..3a592574d 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
@@ -205,7 +205,7 @@ public class I18nInterceptorTest extends TestCase {
}
public void testRealLocalesInParams() throws Exception {
- Locale[] locales = new Locale[] { Locale.CANADA_FRENCH };
+ Locale[] locales = new Locale[]{Locale.CANADA_FRENCH};
assertTrue(locales.getClass().isArray());
prepare(I18nInterceptor.DEFAULT_PARAMETER, locales);
interceptor.intercept(mai);
@@ -294,6 +294,66 @@ public class I18nInterceptorTest extends TestCase {
assertEquals(Locale.US, mai.getInvocationContext().getLocale());
}
+ public void testRequestLocaleWithSupportedLocale() throws Exception {
+ // given
+ interceptor.setSupportedLocale("en,de");
+ prepare(I18nInterceptor.DEFAULT_PARAMETER, "de");
+
+ // when
+ interceptor.intercept(mai);
+
+ // then
+ Locale german = new Locale("de");
+ assertEquals(german,
session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE));
+ assertEquals(german, mai.getInvocationContext().getLocale());
+ }
+
+ public void testUnsupportedRequestLocaleRejected() throws Exception {
+ // given
+ interceptor.setSupportedLocale("en,de");
+ prepare(I18nInterceptor.DEFAULT_PARAMETER, "fr");
+
+ // when
+ interceptor.intercept(mai);
+
+ // then - fr is not supported, should fall back to default
+ assertNull("unsupported locale should not be stored",
session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE));
+ }
+
+ public void testStaleSessionLocaleRejected() throws Exception {
+ // given - session has a stored locale that is no longer supported
+ session.put(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE, Locale.FRENCH);
+ interceptor.setSupportedLocale("en,de");
+
+ // when
+ interceptor.intercept(mai);
+
+ // then - stored fr locale should be discarded since it's not in
supportedLocale
+ assertFalse("stale session locale should be discarded",
+ Locale.FRENCH.equals(mai.getInvocationContext().getLocale()));
+ }
+
+ public void testCookieRequestLocaleWithSupportedLocale() throws Exception {
+ // given
+ interceptor.setSupportedLocale("en,de");
+ interceptor.setLocaleStorage(I18nInterceptor.Storage.COOKIE.name());
+ prepare(I18nInterceptor.DEFAULT_COOKIE_PARAMETER, "de");
+
+ final Cookie cookie = new
Cookie(I18nInterceptor.DEFAULT_COOKIE_ATTRIBUTE, "de");
+ HttpServletResponse response =
EasyMock.createMock(HttpServletResponse.class);
+ response.addCookie(CookieMatcher.eqCookie(cookie));
+ EasyMock.replay(response);
+ ac.put(StrutsStatics.HTTP_RESPONSE, response);
+
+ // when
+ interceptor.intercept(mai);
+
+ // then
+ EasyMock.verify(response);
+ Locale german = new Locale("de");
+ assertEquals(german, mai.getInvocationContext().getLocale());
+ }
+
private void prepare(String key, Serializable value) {
Map<String, Serializable> params = new HashMap<>();
params.put(key, value);
@@ -308,9 +368,9 @@ public class I18nInterceptorTest extends TestCase {
session = new HashMap<>();
ac = ActionContext.of()
- .bind()
- .withSession(session)
- .withParameters(HttpParameters.create().build());
+ .bind()
+ .withSession(session)
+ .withParameters(HttpParameters.create().build());
request = new MockHttpServletRequest();
request.setSession(new MockHttpSession());
@@ -348,8 +408,8 @@ public class I18nInterceptorTest extends TestCase {
public boolean matches(Object argument) {
Cookie cookie = ((Cookie) argument);
return
- (cookie.getName().equals(expected.getName()) &&
- cookie.getValue().equals(expected.getValue()));
+ (cookie.getName().equals(expected.getName()) &&
+ cookie.getValue().equals(expected.getValue()));
}
public static Cookie eqCookie(Cookie ck) {
@@ -359,10 +419,10 @@ public class I18nInterceptorTest extends TestCase {
public void appendTo(StringBuffer buffer) {
buffer
- .append("Received")
- .append(expected.getName())
- .append("/")
- .append(expected.getValue());
+ .append("Received")
+ .append(expected.getName())
+ .append("/")
+ .append(expected.getValue());
}
}