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 c60670b22 WW-5602 fix StreamResult contentCharSet handling (#1511)
c60670b22 is described below
commit c60670b22bbe1e6aa6d1623875be3d50b3d23b43
Author: Lukasz Lenart <[email protected]>
AuthorDate: Wed Jan 14 19:18:27 2026 +0100
WW-5602 fix StreamResult contentCharSet handling (#1511)
Evaluates contentCharSet expression before emptiness check to prevent
malformed content-type headers when expression evaluates to null.
- Parse contentCharSet expression first, then check if result is empty
- Use StringUtils.isNotEmpty() for proper null/empty validation
- Use setCharacterEncoding() instead of appending to content-type string
- Add test for null-evaluating charset expressions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <[email protected]>
---
CLAUDE.md | 1 +
.../java/org/apache/struts2/result/StreamResult.java | 16 +++++++++-------
.../java/org/apache/struts2/result/StreamResultTest.java | 14 ++++++++++++++
3 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 3710f0354..408f7c02d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -148,6 +148,7 @@ Each plugin is a separate Maven module with:
### Important Notes
- **Version**: Currently 6.7.5-SNAPSHOT (release branch:
`release/struts-6-7-x`)
- **Java Compatibility**: Compiled for Java 8, tested through Java 21
+- **Servlet API**: Uses javax.servlet (Java EE), NOT Jakarta EE
(jakarta.servlet)
- **Security**: Always validate inputs and follow OWASP guidelines
- **Performance**: Leverage built-in caching (OGNL expressions, templates)
- **Deprecation**: Some legacy XWork components marked for removal
diff --git a/core/src/main/java/org/apache/struts2/result/StreamResult.java
b/core/src/main/java/org/apache/struts2/result/StreamResult.java
index 9324d5bb5..235152e98 100644
--- a/core/src/main/java/org/apache/struts2/result/StreamResult.java
+++ b/core/src/main/java/org/apache/struts2/result/StreamResult.java
@@ -21,6 +21,7 @@ package org.apache.struts2.result;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
+import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -223,7 +224,7 @@ public class StreamResult extends StrutsResultSupport {
if (inputStream == null) {
String msg = ("Can not find a java.io.InputStream with the
name [" + parsedInputName + "] in the invocation stack. " +
- "Check the <param name=\"inputName\"> tag specified for
this action is correct, not excluded and accepted.");
+ "Check the <param name=\"inputName\"> tag specified
for this action is correct, not excluded and accepted.");
LOG.error(msg);
throw new IllegalArgumentException(msg);
}
@@ -231,11 +232,12 @@ public class StreamResult extends StrutsResultSupport {
HttpServletResponse oResponse =
invocation.getInvocationContext().getServletResponse();
- LOG.debug("Set the content type: {};charset{}", contentType,
contentCharSet);
- if (contentCharSet != null && !contentCharSet.equals("")) {
- oResponse.setContentType(conditionalParse(contentType,
invocation) + ";charset=" + conditionalParse(contentCharSet, invocation));
- } else {
- oResponse.setContentType(conditionalParse(contentType,
invocation));
+ LOG.debug("Set the content type: {};charset={}", contentType,
contentCharSet);
+ String parsedContentType = conditionalParse(contentType,
invocation);
+ String parsedContentCharSet = conditionalParse(contentCharSet,
invocation);
+ oResponse.setContentType(parsedContentType);
+ if (StringUtils.isNotEmpty(parsedContentCharSet)) {
+ oResponse.setCharacterEncoding(parsedContentCharSet);
}
LOG.debug("Set the content length: {}", contentLength);
@@ -267,7 +269,7 @@ public class StreamResult extends StrutsResultSupport {
oOutput = oResponse.getOutputStream();
LOG.debug("Streaming result [{}] type=[{}] length=[{}]
content-disposition=[{}] charset=[{}]",
- inputName, contentType, contentLength, contentDisposition,
contentCharSet);
+ inputName, contentType, contentLength, contentDisposition,
contentCharSet);
LOG.debug("Streaming to output buffer +++ START +++");
byte[] oBuff = new byte[bufferSize];
diff --git a/core/src/test/java/org/apache/struts2/result/StreamResultTest.java
b/core/src/test/java/org/apache/struts2/result/StreamResultTest.java
index a02781812..a7621e8e9 100644
--- a/core/src/test/java/org/apache/struts2/result/StreamResultTest.java
+++ b/core/src/test/java/org/apache/struts2/result/StreamResultTest.java
@@ -120,6 +120,16 @@ public class StreamResultTest extends
StrutsInternalTestCase {
assertEquals("inline", response.getHeader("Content-disposition"));
}
+ public void testStreamResultWithNullCharSetExpression() throws Exception {
+ result.setParse(true);
+ result.setInputName("streamForImage");
+ result.setContentCharSet("${nullCharSetMethod}");
+
+ result.doExecute("helloworld", mai);
+
+ assertEquals("text/plain", response.getContentType());
+ }
+
public void testAllowCacheDefault() throws Exception {
result.setInputName("streamForImage");
@@ -310,6 +320,10 @@ public class StreamResultTest extends
StrutsInternalTestCase {
public String getContentCharSetMethod() {
return "UTF-8";
}
+
+ public String getNullCharSetMethod() {
+ return null;
+ }
}
}