Skip to content

Commit a9f447e

Browse files
committed
Consistent adaptation of HTTP headers on Servlet responses
Includes use of Servlet 6.1 setCharacterEncoding(Charset) Closes gh-36343
1 parent 0841e79 commit a9f447e

File tree

6 files changed

+69
-31
lines changed

6 files changed

+69
-31
lines changed

spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,10 @@ public HttpHeaders getHeaders() {
174174
if (contentType != null && contentType.getCharset() == null) {
175175
String requestEncoding = this.servletRequest.getCharacterEncoding();
176176
if (StringUtils.hasLength(requestEncoding)) {
177-
Charset charSet = Charset.forName(requestEncoding);
177+
Charset charset = Charset.forName(requestEncoding);
178178
Map<String, String> params = new LinkedCaseInsensitiveMap<>();
179179
params.putAll(contentType.getParameters());
180-
params.put("charset", charSet.toString());
180+
params.put("charset", charset.toString());
181181
MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
182182
this.headers.setContentType(mediaType);
183183
}

spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.http.HttpHeaders;
2626
import org.springframework.http.HttpStatusCode;
27+
import org.springframework.http.MediaType;
2728
import org.springframework.util.Assert;
2829

2930
/**
@@ -87,20 +88,42 @@ else if (this.headersWritten) {
8788
@Override
8889
public OutputStream getBody() throws IOException {
8990
this.bodyUsed = true;
90-
this.headersWritten = true;
91+
writeHeaders();
9192
return this.servletResponse.getOutputStream();
9293
}
9394

9495
@Override
9596
public void flush() throws IOException {
96-
this.headersWritten = true;
97+
writeHeaders();
9798
if (this.bodyUsed) {
9899
this.servletResponse.flushBuffer();
99100
}
100101
}
101102

102103
@Override
103104
public void close() {
105+
writeHeaders();
106+
}
107+
108+
private void writeHeaders() {
109+
if (!this.headersWritten) {
110+
// HttpServletResponse exposes some headers as properties: we should include those if not already present
111+
if (this.servletResponse.getContentType() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
112+
this.servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
113+
}
114+
if (this.servletResponse.getCharacterEncoding() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
115+
try {
116+
// Lazy parsing into MediaType
117+
MediaType contentType = this.headers.getContentType();
118+
if (contentType != null) {
119+
this.servletResponse.setCharacterEncoding(contentType.getCharset());
120+
}
121+
}
122+
catch (Exception ex) {
123+
// Leave character encoding unspecified
124+
}
125+
}
126+
}
104127
this.headersWritten = true;
105128
}
106129

spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.io.IOException;
2020
import java.nio.ByteBuffer;
21-
import java.nio.charset.Charset;
2221

2322
import jakarta.servlet.AsyncContext;
2423
import jakarta.servlet.AsyncEvent;
@@ -122,31 +121,34 @@ protected void applyHeaders() {
122121
}
123122

124123
protected void adaptHeaders(boolean removeAdaptedHeaders) {
125-
MediaType contentType = null;
126-
try {
127-
contentType = getHeaders().getContentType();
128-
}
129-
catch (Exception ex) {
130-
String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
131-
this.response.setContentType(rawContentType);
132-
}
133-
if (this.response.getContentType() == null && contentType != null) {
134-
this.response.setContentType(contentType.toString());
124+
HttpHeaders headers = getHeaders();
125+
// HttpServletResponse exposes some headers as properties: we should include those if not already present
126+
127+
if (this.response.getContentType() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
128+
this.response.setContentType(headers.getFirst(HttpHeaders.CONTENT_TYPE));
135129
}
136130

137-
Charset charset = (contentType != null ? contentType.getCharset() : null);
138-
if (this.response.getCharacterEncoding() == null && charset != null) {
139-
this.response.setCharacterEncoding(charset.name());
131+
if (this.response.getCharacterEncoding() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
132+
try {
133+
// Lazy parsing into MediaType
134+
MediaType contentType = headers.getContentType();
135+
if (contentType != null) {
136+
this.response.setCharacterEncoding(contentType.getCharset());
137+
}
138+
}
139+
catch (Exception ex) {
140+
// Leave character encoding unspecified
141+
}
140142
}
141143

142-
long contentLength = getHeaders().getContentLength();
144+
long contentLength = headers.getContentLength();
143145
if (contentLength != -1) {
144146
this.response.setContentLengthLong(contentLength);
145147
}
146148

147149
if (removeAdaptedHeaders) {
148-
getHeaders().remove(HttpHeaders.CONTENT_TYPE);
149-
getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
150+
headers.remove(HttpHeaders.CONTENT_TYPE);
151+
headers.remove(HttpHeaders.CONTENT_LENGTH);
150152
}
151153
}
152154

@@ -352,7 +354,6 @@ protected boolean isFlushPending() {
352354

353355
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
354356

355-
356357
public ResponseBodyProcessor() {
357358
super(request.getLogPrefix());
358359
}

spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.http.HttpHeaders;
3030
import org.springframework.http.HttpMethod;
3131
import org.springframework.http.HttpStatusCode;
32+
import org.springframework.http.MediaType;
3233
import org.springframework.util.CollectionUtils;
3334
import org.springframework.util.LinkedMultiValueMap;
3435
import org.springframework.util.MultiValueMap;
@@ -51,13 +52,13 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
5152

5253
private final MultiValueMap<String, Cookie> cookies;
5354

55+
5456
protected AbstractServerResponse(
5557
HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap<String, Cookie> cookies) {
5658

5759
this.statusCode = statusCode;
5860
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
59-
this.cookies =
60-
CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(cookies));
61+
this.cookies = CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(cookies));
6162
}
6263

6364
@Override
@@ -110,14 +111,22 @@ private void writeHeaders(HttpServletResponse servletResponse) {
110111
servletResponse.addHeader(headerName, headerValue);
111112
}
112113
});
114+
113115
// HttpServletResponse exposes some headers as properties: we should include those if not already present
114-
if (servletResponse.getContentType() == null && this.headers.getContentType() != null) {
115-
servletResponse.setContentType(this.headers.getContentType().toString());
116+
if (servletResponse.getContentType() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
117+
servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
116118
}
117-
if (servletResponse.getCharacterEncoding() == null &&
118-
this.headers.getContentType() != null &&
119-
this.headers.getContentType().getCharset() != null) {
120-
servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
119+
if (servletResponse.getCharacterEncoding() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
120+
try {
121+
// Lazy parsing into MediaType
122+
MediaType contentType = this.headers.getContentType();
123+
if (contentType != null) {
124+
servletResponse.setCharacterEncoding(contentType.getCharset());
125+
}
126+
}
127+
catch (Exception ex) {
128+
// Leave character encoding unspecified
129+
}
121130
}
122131
}
123132

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@ public void setCharacterEncoding(String charset) {
458458
// ignore
459459
}
460460

461+
@Override
462+
public void setCharacterEncoding(Charset encoding) {
463+
// ignore
464+
}
465+
461466
@Override
462467
public void setContentLength(int len) {
463468
// ignore

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ protected void prepareResponse(HttpServletRequest request, HttpServletResponse r
374374

375375
setResponseContentType(request, response);
376376
if (this.charset != null) {
377-
response.setCharacterEncoding(this.charset.name());
377+
response.setCharacterEncoding(this.charset);
378378
}
379379
}
380380

0 commit comments

Comments
 (0)