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 "));