diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index a056a68a01..7c973bf64e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -266,25 +266,25 @@ public class ServletServerHttpRequest implements ServerHttpRequest { */ private InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); - Writer writer = new OutputStreamWriter(bos, FORM_CHARSET); + Charset charset = getFormCharset(); + Writer writer = new OutputStreamWriter(bos, charset); Map form = request.getParameterMap(); - for (Iterator> entryIterator = form.entrySet().iterator(); entryIterator.hasNext();) { - Map.Entry entry = entryIterator.next(); - String name = entry.getKey(); + for (Iterator> entryItr = form.entrySet().iterator(); entryItr.hasNext();) { + Map.Entry entry = entryItr.next(); List values = Arrays.asList(entry.getValue()); - for (Iterator valueIterator = values.iterator(); valueIterator.hasNext();) { - String value = valueIterator.next(); - writer.write(URLEncoder.encode(name, FORM_CHARSET)); + for (Iterator valueItr = values.iterator(); valueItr.hasNext();) { + String value = valueItr.next(); + writer.write(URLEncoder.encode(entry.getKey(), charset)); if (value != null) { writer.write('='); - writer.write(URLEncoder.encode(value, FORM_CHARSET)); - if (valueIterator.hasNext()) { + writer.write(URLEncoder.encode(value, charset)); + if (valueItr.hasNext()) { writer.write('&'); } } } - if (entryIterator.hasNext()) { + if (entryItr.hasNext()) { writer.append('&'); } } @@ -298,6 +298,19 @@ public class ServletServerHttpRequest implements ServerHttpRequest { return new ByteArrayInputStream(bytes); } + private Charset getFormCharset() { + try { + MediaType contentType = getHeaders().getContentType(); + if (contentType != null && contentType.getCharset() != null) { + return contentType.getCharset(); + } + } + catch (Exception ex) { + // ignore + } + return FORM_CHARSET; + } + private final class AttributesMap extends AbstractMap { diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index 4215d95fc1..a4113ac023 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.http.server; import java.io.IOException; import java.net.URI; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.List; @@ -182,9 +183,7 @@ class ServletServerHttpRequestTests { mockRequest.addParameter("name 2", "value 2+1", "value 2+2"); mockRequest.addParameter("name 3", (String) null); - byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); - byte[] content = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3".getBytes(StandardCharsets.UTF_8); - assertThat(result).as("Invalid content returned").isEqualTo(content); + assertFormContent("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"); } @Test @@ -192,9 +191,7 @@ class ServletServerHttpRequestTests { mockRequest.setContentType("application/x-www-form-urlencoded; charset=UTF-8"); mockRequest.setMethod("POST"); - byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); - byte[] content = "".getBytes(StandardCharsets.UTF_8); - assertThat(result).as("Invalid content returned").isEqualTo(content); + assertFormContent(""); } @Test // gh-31327 @@ -206,9 +203,7 @@ class ServletServerHttpRequestTests { mockRequest.setContent("foo=bar".getBytes(StandardCharsets.UTF_8)); mockRequest.addHeader("Content-Length", 7); - byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); - byte[] content = "foo=bar".getBytes(StandardCharsets.UTF_8); - assertThat(result).as("Invalid content returned").isEqualTo(content); + assertFormContent("foo=bar"); } @Test // gh-32471 @@ -219,9 +214,25 @@ class ServletServerHttpRequestTests { mockRequest.addParameter("lastName", "Test@er"); mockRequest.addHeader("Content-Length", 26); + int contentLength = assertFormContent("name=Test&lastName=Test%40er"); + assertThat(request.getHeaders().getContentLength()).isEqualTo(contentLength); + } + + @Test // gh-34675 + void getFormBodyWithNotUtf8Charset() throws IOException { + String charset = "windows-1251"; + mockRequest.setContentType("application/x-www-form-urlencoded; charset=" + charset); + mockRequest.setMethod("POST"); + mockRequest.addParameter("x", URLDecoder.decode("%e0%e0%e0", charset)); + + assertFormContent("x=%E0%E0%E0"); + } + + private int assertFormContent(String expected) throws IOException { byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); - assertThat(result).isEqualTo("name=Test&lastName=Test%40er".getBytes(StandardCharsets.UTF_8)); - assertThat(request.getHeaders().getContentLength()).isEqualTo(result.length); + byte[] content = expected.getBytes(StandardCharsets.UTF_8); + assertThat(result).as("Invalid content returned").isEqualTo(content); + return result.length; } @Test