Summary
FormPostRedirectStrategy.sendRedirect(req, res, url) writes each query parameter into a hidden form input:
for (Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
String name = entry.getKey();
for (String value : entry.getValue()) {
... HtmlUtils.htmlEscape(value) ...
}
}
UriComponents.getQueryParams() returns values in the percent-encoded form they had in the source URL. On form POST, the browser encodes these again on submit, and so every percent-escape in the original URL becomes doubled and the value is corrupted.
Reproducer
String url = UriComponentsBuilder.fromUriString("https://example.com/cb")
.queryParam("payload", UriUtils.encode("a+b/c=", StandardCharsets.UTF_8))
.build(true).toUriString();
// url == https://example.com/cb?payload=a%2Bb%2Fc%3D
redirectStrategy.sendRedirect(req, res, url);
// Actual: <input name="payload" type="hidden" value="a%2Bb%2Fc%3D" />
// Expected: <input name="payload" type="hidden" value="a+b/c=" />
When the browser submits this form, the receiving server gets payload=a%252Bb%252Fc%253D, decodes once, and reads a%2Bb%2Fc%3D — not the original a+b/c=.
Suggested Fix
Decode each value before HTML-escaping:
for (Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
String name = UriUtils.decode(entry.getKey(), StandardCharsets.UTF_8);
for (String raw : entry.getValue()) {
if (raw == null) {
continue;
}
String value = UriUtils.decode(raw, StandardCharsets.UTF_8);
...
}
}
Summary
FormPostRedirectStrategy.sendRedirect(req, res, url)writes each query parameter into a hidden form input:UriComponents.getQueryParams()returns values in the percent-encoded form they had in the source URL. On form POST, the browser encodes these again on submit, and so every percent-escape in the original URL becomes doubled and the value is corrupted.Reproducer
When the browser submits this form, the receiving server gets
payload=a%252Bb%252Fc%253D, decodes once, and readsa%2Bb%2Fc%3D— not the originala+b/c=.Suggested Fix
Decode each value before HTML-escaping: