When injecting the @RequestBody
for x-www-form-urlencoded
POST requests, Spring tries to recreate the original body payload via javax.servlet.ServletRequest.getParameterMap()
in org.springframework.http.server.ServletServerHttpRequest#getBodyFromServletRequestParameters
.
This operation is imperfect because there the URL encode operation does not entirely revert the previous decoding. An example is the %2A
<-> *
enconding/decoding. %2A
is decoded into *
but getBodyFromServletRequestParameters
fails to transform it back into %2A
, thus failing to produce the exact original payload. This is critical for for example, validating payload signatures, which require the exact same content that was used by the client that made the request.
Version: Spring Boot 5.2.3
Comment From: andredasilvapinto
This is likely caused by the behaviour from Java's URLEncoder/URLDecoder classes:
It decodes asterisks:
System.out.println(URLDecoder.decode("%2A"));
> *
but doesn't encode them:
System.out.println(URLEncoder.encode("*"));
> *
Spring shouldn't rely on the reversability of this encoding to provide a way to access the original body payload. Is there any workaround that is not affected by this bug?
Comment From: drgnchan
This is likely caused by the behaviour from Java's URLEncoder/URLDecoder classes:
It decodes asterisks:
``` System.out.println(URLDecoder.decode("%2A"));
* ```
but doesn't encode them:
``` System.out.println(URLEncoder.encode("*"));
* ```
"" is encoded to "" because it conforms to the rules which exist in URLEncoder.java's doc.
Utility class for HTML form decoding. This class contains static methods for decoding a String from the application/x-www-form-urlencoded MIME format. The conversion process is the reverse of that used by the URLEncoder class. It is assumed that all characters in the encoded string are one of the following: "a" through "z", "A" through "Z", "0" through "9", and "-", "_", ".", and "*". The character "%" is allowed but is interpreted as the start of a special escaped sequence. The following rules are applied in the conversion: The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain the same.
The special characters ".", "-", "*", and "_" remain the same.
The plus sign "+" is converted into a space character " " . A sequence of the form "%xy" will be treated as representing a byte where xy is the two-digit hexadecimal representation of the 8 bits. Then, all substrings that contain one or more of these byte sequences consecutively will be replaced by the character(s) whose encoding would result in those consecutive bytes. The encoding scheme used to decode these characters may be specified, or if unspecified, the default encoding of the platform will be used. There are two possible ways in which this decoder could deal with illegal strings. It could either leave illegal characters alone or it could throw an IllegalArgumentException. Which approach the decoder takes is left to the implementation. Since: 1.2 Author: Mark Chamness, Michael McCloskey
Comment From: rstoyanchev
I can't find anything in the spec about encoding "*"
. I do understand the point that if a client chose to encode it, then the payload won't be identical as what was received. For full control, you can inject InputStream
into the controller method and consume it directly.