From 428db20b3c2037b72e0efbfde79d932eac7b0847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=B3=95=E2=B2=9B=CF=84=E2=B2=89=E2=B2=85=E2=B2=A5?= =?UTF-8?q?=E2=B2=89=E2=B3=8F=CF=84=E2=B2=9F=E2=B2=85=20=F0=9F=95=B5?= =?UTF-8?q?=F0=9F=8F=BB?= <192411347+g0w6y@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:35:14 +0530 Subject: [PATCH 1/2] WW-5845 Validate the JSONP callback parameter name before wrapping the response in it JSONResult.addCallbackIfApplicable and JSONInterceptor.addCallbackIfApplicable built the JSONP wrapper as callbackName + "(" + json + ")", where callbackName is the raw, unvalidated callbackParameter query value -- no character restriction. Since the response is meant to be loaded as ")); + assertFalse(JSONUtil.isValidJsonpCallbackName("")); + assertFalse(JSONUtil.isValidJsonpCallbackName(null)); + assertFalse(JSONUtil.isValidJsonpCallbackName("a".repeat(129))); + } + } From 89ea1696fcf5ca91f1bde10731d65ff38f67cc66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=B3=95=E2=B2=9B=CF=84=E2=B2=89=E2=B2=85=E2=B2=A5?= =?UTF-8?q?=E2=B2=89=E2=B3=8F=CF=84=E2=B2=9F=E2=B2=85=20=F0=9F=95=B5?= =?UTF-8?q?=F0=9F=8F=BB?= <192411347+g0w6y@users.noreply.github.com> Date: Sun, 8 Nov 2026 08:55:42 +0530 Subject: [PATCH 2/2] WW-5845 Address review: normalize the logged callback name, cover the SMD arm, trim comments - log the rejected callback name through normalizeSpace in JSONInterceptor and JSONResult, like the other request-derived values in these classes - JSONInterceptorTest: add a rejection and a control test for the SMD arm - reduce the JSONUtil Javadoc and the new test comments to the contract - move the callback pattern next to the other static final fields --- .../apache/struts2/json/JSONInterceptor.java | 3 +- .../org/apache/struts2/json/JSONResult.java | 3 +- .../org/apache/struts2/json/JSONUtil.java | 22 +++++--------- .../struts2/json/JSONInterceptorTest.java | 29 +++++++++++++++++++ .../apache/struts2/json/JSONResultTest.java | 12 +------- .../org/apache/struts2/json/JSONUtilTest.java | 9 ------ 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java index af4596e250..53d16dea96 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java @@ -543,8 +543,7 @@ protected String addCallbackIfApplicable(HttpServletRequest request, String json String callbackName = request.getParameter(callbackParameter); if ((callbackName != null) && (callbackName.length() > 0)) { if (!JSONUtil.isValidJsonpCallbackName(callbackName)) { - LOG.warn("Rejecting JSONP callback parameter '{}' -- not a valid (dotted) " - + "JS identifier, refusing to wrap the response with it", callbackName); + LOG.warn("JSONP callback parameter [{}] is not a valid identifier; rejected", normalizeSpace(callbackName)); return json; } json = callbackName + "(" + json + ")"; diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java index e194def48a..6c17e82e8f 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java @@ -283,8 +283,7 @@ protected String addCallbackIfApplicable(HttpServletRequest request, String json String callbackName = request.getParameter(callbackParameter); if (StringUtils.isNotEmpty(callbackName)) { if (!JSONUtil.isValidJsonpCallbackName(callbackName)) { - LOG.warn("Rejecting JSONP callback parameter '{}' -- not a valid (dotted) " - + "JS identifier, refusing to wrap the response with it", callbackName); + LOG.warn("JSONP callback parameter [{}] is not a valid identifier; rejected", StringUtils.normalizeSpace(callbackName)); return json; } json = callbackName + "(" + json + ")"; diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONUtil.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONUtil.java index 82bd49953e..c0c8ff0069 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONUtil.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONUtil.java @@ -62,6 +62,9 @@ public class JSONUtil { /** Chunk size used to read input incrementally while applying the length limit. */ private static final int READ_CHUNK_SIZE = 8192; + private static final Pattern JSONP_CALLBACK_PATTERN = + Pattern.compile("[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*"); + private JSONReader reader; private JSONWriter writer; @@ -566,19 +569,11 @@ public static boolean isGzipInRequest(HttpServletRequest request) { } /** - * A JSONP {@code callback} request parameter value must be a (dotted) JavaScript - * identifier, matching every real-world JSONP server implementation's own validation. - * {@link JSONResult#addCallbackIfApplicable(HttpServletRequest, String)} and - * {@link JSONInterceptor#addCallbackIfApplicable(HttpServletRequest, String)} both - * concatenate the callback parameter's raw value directly around the serialized JSON - * ({@code callbackName + "(" + json + ")"}) with no check at all -- any character, - * including {@code )}, {@code ;}, and further script text, passes straight into the - * response body that a consumer loads as a {@code "));