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());
         }
     }
 

Reply via email to