This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5668-i18n-cache-bounds-6x in repository https://gitbox.apache.org/repos/asf/struts.git
commit d3af2dc2f5075ac65ce7ab50d9e818405d2b1c5c Author: Lukasz Lenart <[email protected]> AuthorDate: Sat Aug 1 09:24:01 2026 +0200 WW-5668 Add opt-in request-locale resolution consistency to Dispatcher Adds struts.locale.validateRequestLocale (default false) so request-derived locales can be restricted to the JVM's available-locale set, matching what I18nInterceptor already applies to its own locale sources. Co-Authored-By: Claude Opus 5 <[email protected]> --- .../java/org/apache/struts2/StrutsConstants.java | 8 ++++ .../org/apache/struts2/dispatcher/Dispatcher.java | 48 +++++++++++++++++++++- .../org/apache/struts2/default.properties | 3 ++ .../apache/struts2/dispatcher/DispatcherTest.java | 40 ++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 22c753bab..d6b6a5b19 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -100,6 +100,14 @@ public final class StrutsConstants { /** The default locale for the Struts application */ public static final String STRUTS_LOCALE = "struts.locale"; + /** + * When enabled, request-derived locales (from {@code Accept-Language}, used when {@code struts.locale} is + * unset) are restricted to the JVM's available-locale set; unavailable values fall back to the default. + * + * @since 6.11.0 + */ + public static final String STRUTS_LOCALE_VALIDATE_REQUEST = "struts.locale.validateRequestLocale"; + /** Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic */ public static final String STRUTS_DISPATCHER_PARAMETERSWORKAROUND = "struts.dispatcher.parametersWorkaround"; diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java index 55f0228ba..4b774dedf 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java @@ -152,6 +152,11 @@ public class Dispatcher { */ private String defaultLocale; + /** + * Store state of {@link StrutsConstants#STRUTS_LOCALE_VALIDATE_REQUEST} setting. + */ + private boolean validateRequestLocale = false; + /** * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting. */ @@ -311,6 +316,18 @@ public class Dispatcher { defaultLocale = val; } + /** + * Modify state of {@link StrutsConstants#STRUTS_LOCALE_VALIDATE_REQUEST} setting. + * + * @param val New setting + * + * @since 6.11.0 + */ + @Inject(value = StrutsConstants.STRUTS_LOCALE_VALIDATE_REQUEST, required = false) + public void setValidateRequestLocale(String val) { + validateRequestLocale = Boolean.parseBoolean(val); + } + /** * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting. * @@ -950,7 +967,7 @@ public class Dispatcher { locale = LocaleUtils.toLocale(defaultLocale); } catch (IllegalArgumentException e) { try { - locale = request.getLocale(); + locale = resolveRequestLocale(request); LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]", defaultLocale, locale), e); } catch (RuntimeException rex) { @@ -961,7 +978,7 @@ public class Dispatcher { } } else { try { - locale = request.getLocale(); + locale = resolveRequestLocale(request); } catch (RuntimeException rex) { LOG.warn("Cannot get locale from HTTP Request, falling back to system default locale", rex); locale = Locale.getDefault(); @@ -970,6 +987,33 @@ public class Dispatcher { return locale; } + /** + * Resolves the request locale. When {@code struts.locale.validateRequestLocale} is enabled and the + * request locale is not part of the JVM's available-locale set, falls back to the configured + * {@code struts.locale} when set and parseable, otherwise the JVM default. When disabled (default), + * returns the request locale unchanged. + * + * @param request the current request + * @return the locale to use for this request + * + * @since 6.11.0 + */ + protected Locale resolveRequestLocale(HttpServletRequest request) { + Locale locale = request.getLocale(); + if (!validateRequestLocale || LocaleUtils.isAvailableLocale(locale)) { + return locale; + } + if (defaultLocale != null) { + try { + return LocaleUtils.toLocale(defaultLocale); + } catch (IllegalArgumentException e) { + LOG.debug("Configured 'struts.locale' = [{}] is not parseable; falling back to system default", defaultLocale); + } + } + LOG.debug("Request locale [{}] is not available; falling back to system default locale", locale); + return Locale.getDefault(); + } + /** * Return the path to save uploaded files to (this is configurable). * diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index e48bbd015..9bf2ae038 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -24,6 +24,9 @@ ### This can be used to set your default locale and encoding scheme # struts.locale=en_US +### When true, restrict request-derived locales (Accept-Language, used when struts.locale is unset) to the +### JVM's available-locale set; unavailable values fall back to the default locale. Defaults to false. +struts.locale.validateRequestLocale=false struts.i18n.encoding=UTF-8 ### if specified, the default object factory can be overridden here diff --git a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java index 6cce6e601..96e6bd2d1 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java @@ -570,6 +570,46 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase { assertEquals(Locale.getDefault(), context.getLocale()); // Expect the system default value when Mock request access fails. } + @Test + public void testValidateRequestLocaleOffPassesThrough() { + initDispatcher(new HashMap<>()); + dispatcher.setDefaultLocale(null); // Force struts.locale unset; the test-config default would otherwise mask the request locale. + HttpServletRequest request = mock(HttpServletRequest.class); + // A syntactically valid but not JVM-available locale. + Locale exotic = new Locale("en", "US", "xzz99"); + when(request.getLocale()).thenReturn(exotic); + + assertEquals("Default off must pass the request locale through unchanged", + exotic, dispatcher.getLocale(request)); + } + + @Test + public void testValidateRequestLocaleOnKeepsAvailableLocale() { + Map<String, String> params = new HashMap<>(); + params.put(StrutsConstants.STRUTS_LOCALE_VALIDATE_REQUEST, "true"); + initDispatcher(params); + dispatcher.setDefaultLocale(null); // Force struts.locale unset; the test-config default would otherwise mask the request locale. + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getLocale()).thenReturn(Locale.UK); + + assertEquals("Available request locale must be kept", Locale.UK, dispatcher.getLocale(request)); + } + + @Test + public void testValidateRequestLocaleOnFallsBackForUnavailableLocale() { + Map<String, String> params = new HashMap<>(); + params.put(StrutsConstants.STRUTS_LOCALE_VALIDATE_REQUEST, "true"); + initDispatcher(params); + dispatcher.setDefaultLocale(null); // Force struts.locale unset; the test-config default would otherwise mask the request locale. + HttpServletRequest request = mock(HttpServletRequest.class); + Locale exotic = new Locale("en", "US", "xzz99"); + when(request.getLocale()).thenReturn(exotic); + + // struts.locale unset in this dispatcher -> fall back to the JVM default. + assertEquals("Unavailable request locale must fall back to system default", + Locale.getDefault(), dispatcher.getLocale(request)); + } + @Test public void dispatcherReinjectedAfterReload() { HttpServletRequest request = mock(HttpServletRequest.class);
