Skip to content

Commit f529386

Browse files
committed
Merge pull request #31781 from rstoyanchev
* pr/31781: Write form data without charset parameter Closes gh-31781
2 parents 0899837 + b871581 commit f529386

File tree

8 files changed

+44
-53
lines changed

8 files changed

+44
-53
lines changed

spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,14 +60,9 @@
6060
public class FormHttpMessageWriter extends LoggingCodecSupport
6161
implements HttpMessageWriter<MultiValueMap<String, String>> {
6262

63-
/**
64-
* The default charset used by the writer.
65-
*/
63+
/** The default charset used by the writer. */
6664
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
6765

68-
private static final MediaType DEFAULT_FORM_DATA_MEDIA_TYPE =
69-
new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);
70-
7166
private static final List<MediaType> MEDIA_TYPES =
7267
Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED);
7368

@@ -126,7 +121,7 @@ public Mono<Void> write(Publisher<? extends MultiValueMap<String, String>> input
126121
mediaType = getMediaType(mediaType);
127122
message.getHeaders().setContentType(mediaType);
128123

129-
Charset charset = mediaType.getCharset() != null ? mediaType.getCharset() : getDefaultCharset();
124+
Charset charset = (mediaType.getCharset() != null ? mediaType.getCharset() : getDefaultCharset());
130125

131126
return Mono.from(inputStream).flatMap(form -> {
132127
logFormData(form, hints);
@@ -138,16 +133,22 @@ public Mono<Void> write(Publisher<? extends MultiValueMap<String, String>> input
138133
});
139134
}
140135

136+
/**
137+
* Return the content type used to write forms, either the given media type
138+
* or otherwise {@code application/x-www-form-urlencoded}.
139+
* @param mediaType the media type passed to {@link #write}, or {@code null}
140+
* @return the content type to use
141+
*/
141142
protected MediaType getMediaType(@Nullable MediaType mediaType) {
142143
if (mediaType == null) {
143-
return DEFAULT_FORM_DATA_MEDIA_TYPE;
144-
}
145-
else if (mediaType.getCharset() == null) {
146-
return new MediaType(mediaType, getDefaultCharset());
144+
return MediaType.APPLICATION_FORM_URLENCODED;
147145
}
148-
else {
149-
return mediaType;
146+
// Some servers don't handle charset parameter and spec is unclear,
147+
// Add it only if it is not DEFAULT_CHARSET.
148+
if (mediaType.getCharset() == null && this.defaultCharset != DEFAULT_CHARSET) {
149+
return new MediaType(mediaType, this.defaultCharset);
150150
}
151+
return mediaType;
151152
}
152153

153154
private void logFormData(MultiValueMap<String, String> form, Map<String, Object> hints) {

spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,9 @@
154154
*/
155155
public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
156156

157-
/**
158-
* The default charset used by the converter.
159-
*/
157+
/** The default charset used by the converter. */
160158
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
161159

162-
private static final MediaType DEFAULT_FORM_DATA_MEDIA_TYPE =
163-
new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);
164-
165160

166161
private List<MediaType> supportedMediaTypes = new ArrayList<>();
167162

@@ -387,14 +382,13 @@ private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType co
387382
return false;
388383
}
389384

390-
private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType contentType,
385+
private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType mediaType,
391386
HttpOutputMessage outputMessage) throws IOException {
392387

393-
contentType = getFormContentType(contentType);
394-
outputMessage.getHeaders().setContentType(contentType);
388+
mediaType = getFormContentType(mediaType);
389+
outputMessage.getHeaders().setContentType(mediaType);
395390

396-
Charset charset = contentType.getCharset();
397-
Assert.notNull(charset, "No charset"); // should never occur
391+
Charset charset = (mediaType.getCharset() != null ? mediaType.getCharset() : this.charset);
398392

399393
byte[] bytes = serializeForm(formData, charset).getBytes(charset);
400394
outputMessage.getHeaders().setContentLength(bytes.length);
@@ -418,26 +412,22 @@ public boolean repeatable() {
418412
}
419413

420414
/**
421-
* Return the content type used to write forms, given the preferred content type.
422-
* By default, this method returns the given content type, but adds the
423-
* {@linkplain #setCharset(Charset) charset} if it does not have one.
424-
* If {@code contentType} is {@code null},
425-
* {@code application/x-www-form-urlencoded; charset=UTF-8} is returned.
426-
* <p>Subclasses can override this method to change this behavior.
427-
* @param contentType the preferred content type (can be {@code null})
428-
* @return the content type to be used
415+
* Return the content type used to write forms, either the given content type
416+
* or otherwise {@code application/x-www-form-urlencoded}.
417+
* @param contentType the content type passed to {@link #write}, or {@code null}
418+
* @return the content type to use
429419
* @since 5.2.2
430420
*/
431421
protected MediaType getFormContentType(@Nullable MediaType contentType) {
432422
if (contentType == null) {
433-
return DEFAULT_FORM_DATA_MEDIA_TYPE;
423+
return MediaType.APPLICATION_FORM_URLENCODED;
434424
}
435-
else if (contentType.getCharset() == null) {
425+
// Some servers don't handle charset parameter and spec is unclear,
426+
// Add it only if it is not DEFAULT_CHARSET.
427+
if (contentType.getCharset() == null && this.charset != DEFAULT_CHARSET) {
436428
return new MediaType(contentType, this.charset);
437429
}
438-
else {
439-
return contentType;
440-
}
430+
return contentType;
441431
}
442432

443433
protected String serializeForm(MultiValueMap<String, Object> formData, Charset charset) {

spring-web/src/test/java/org/springframework/http/codec/FormHttpMessageWriterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void writeForm() {
8888
.expectComplete()
8989
.verify();
9090
HttpHeaders headers = response.getHeaders();
91-
assertThat(headers.getContentType().toString()).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
91+
assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED);
9292
assertThat(headers.getContentLength()).isEqualTo(expected.length());
9393
}
9494

spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.web.testfixture.http.MockHttpInputMessage;
4848
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
4949

50+
import static java.nio.charset.StandardCharsets.UTF_8;
5051
import static org.assertj.core.api.Assertions.assertThat;
5152
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
5253
import static org.springframework.http.MediaType.APPLICATION_JSON;
@@ -93,7 +94,7 @@ void canWrite() {
9394
assertCanWrite(MULTIPART_FORM_DATA);
9495
assertCanWrite(MULTIPART_MIXED);
9596
assertCanWrite(MULTIPART_RELATED);
96-
assertCanWrite(new MediaType("multipart", "form-data", StandardCharsets.UTF_8));
97+
assertCanWrite(new MediaType("multipart", "form-data", UTF_8));
9798
assertCanWrite(MediaType.ALL);
9899
assertCanWrite(null);
99100
}
@@ -141,10 +142,10 @@ void writeForm() throws IOException {
141142
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
142143
this.converter.write(body, APPLICATION_FORM_URLENCODED, outputMessage);
143144

144-
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8))
145+
assertThat(outputMessage.getBodyAsString(UTF_8))
145146
.as("Invalid result").isEqualTo("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3");
146-
assertThat(outputMessage.getHeaders().getContentType().toString())
147-
.as("Invalid content-type").isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
147+
assertThat(outputMessage.getHeaders().getContentType())
148+
.as("Invalid content-type").isEqualTo(APPLICATION_FORM_URLENCODED);
148149
assertThat(outputMessage.getHeaders().getContentLength())
149150
.as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length);
150151
}
@@ -178,7 +179,7 @@ public String getFilename() {
178179
parts.add("json", entity);
179180

180181
Map<String, String> parameters = new LinkedHashMap<>(2);
181-
parameters.put("charset", StandardCharsets.UTF_8.name());
182+
parameters.put("charset", UTF_8.name());
182183
parameters.put("foo", "bar");
183184

184185
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
@@ -260,7 +261,7 @@ public String getFilename() {
260261
parts.add("xml", entity);
261262

262263
Map<String, String> parameters = new LinkedHashMap<>(2);
263-
parameters.put("charset", StandardCharsets.UTF_8.name());
264+
parameters.put("charset", UTF_8.name());
264265
parameters.put("foo", "bar");
265266

266267
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
@@ -323,8 +324,8 @@ public void writeMultipartOrder() throws Exception {
323324
parts.add("part2", entity);
324325

325326
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
326-
this.converter.setMultipartCharset(StandardCharsets.UTF_8);
327-
this.converter.write(parts, new MediaType("multipart", "form-data", StandardCharsets.UTF_8), outputMessage);
327+
this.converter.setMultipartCharset(UTF_8);
328+
this.converter.write(parts, new MediaType("multipart", "form-data", UTF_8), outputMessage);
328329

329330
final MediaType contentType = outputMessage.getHeaders().getContentType();
330331
assertThat(contentType.getParameter("boundary")).as("No boundary found").isNotNull();

spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ private void assertFilePart(Buffer buffer, String disposition, String boundary,
192192
}
193193

194194
private MockResponse formRequest(RecordedRequest request) {
195-
assertThat(request.getHeader(CONTENT_TYPE)).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
195+
assertThat(request.getHeader(CONTENT_TYPE)).isEqualTo("application/x-www-form-urlencoded");
196196
assertThat(request.getBody().readUtf8()).contains("name+1=value+1", "name+2=value+2%2B1", "name+2=value+2%2B2");
197197
return new MockResponse().setResponseCode(200);
198198
}
@@ -235,7 +235,7 @@ private MockResponse putRequest(RecordedRequest request, String expectedRequestC
235235
protected class TestDispatcher extends Dispatcher {
236236

237237
@Override
238-
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
238+
public MockResponse dispatch(RecordedRequest request) {
239239
try {
240240
byte[] helloWorldBytes = helloWorld.getBytes(StandardCharsets.UTF_8);
241241

spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ void formData(MockWebServer server, Service service) throws Exception {
188188
service.postForm(map);
189189

190190
RecordedRequest request = server.takeRequest();
191-
assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
191+
assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded");
192192
assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1&param2=value+2");
193193
}
194194

spring-web/src/test/kotlin/org/springframework/web/client/support/KotlinRestTemplateHttpServiceProxyTests.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ class KotlinRestTemplateHttpServiceProxyTests {
136136
testService.postForm(map)
137137

138138
val request = server.takeRequest()
139-
assertThat(request.headers["Content-Type"])
140-
.isEqualTo("application/x-www-form-urlencoded;charset=UTF-8")
139+
assertThat(request.headers["Content-Type"]).isEqualTo("application/x-www-form-urlencoded")
141140
assertThat(request.body.readUtf8()).isEqualTo("param1=value+1&param2=value+2")
142141
}
143142

spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ void formData() throws Exception {
145145
initService().postForm(map);
146146

147147
RecordedRequest request = this.server.takeRequest();
148-
assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
148+
assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded");
149149
assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1&param2=value+2");
150150
}
151151

0 commit comments

Comments
 (0)