Summary
WebTestClientRequestConverter.createRequestCookie(String) parses the Cookie header value with a naive split("=") and unconditionally accesses components[1]. This leads to two related problems:
- Data corruption — when a cookie value legitimately contains
= (e.g. Base64-padded session IDs, JWTs in some encodings), only the substring before the first = is captured as the value, and the remainder is silently
dropped.
ArrayIndexOutOfBoundsException — when a cookie header value contains no = at all, components[1] throws.
Affected code
spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java, lines 107–110:
private RequestCookie createRequestCookie(String header) {
String[] components = header.split("=");
return new RequestCookie(components[0], components[1]);
}
There is no length check on components, and split("=") is called without a limit, so any = inside the value is also treated as a delimiter.
For comparison, MockMvcRequestConverter.extractCookies(...) reads cookies from the already-parsed MockHttpServletRequest#getCookies() and never touches the raw header string, so the equivalent path on the MockMvc side is
unaffected.
Scenario 1 — Cookie value containing = (data corruption)
= is a valid character inside a cookie value per RFC 6265 §4.1.1 (only ;, ,, whitespace, and a few control characters are excluded). It is also extremely
common in practice: Base64 padding (abc=, abcd==) is the obvious example, and many session/JWT cookies end up containing it.
This is reachable through the standard WebTestClient.cookie(name, value) API:
ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null))
.configureClient()
.baseUrl("http://localhost")
.build()
.get()
.uri("/foo")
.cookie("sessionId", "YWJjZGVm==") // Base64-padded value
.exchange()
.expectBody()
.returnResult();
OperationRequest request = this.converter.convert(result);
// Expected: value == "YWJjZGVm=="
// Actual: value == "YWJjZGVm" (everything from the first '=' onward is lost)
assertThat(request.getCookies()).extracting("value").containsExactly("YWJjZGVm==");
I verified this locally against main (commit 631b6c22); the assertion fails with:
Expecting actual:
["YWJjZGVm"]
to contain exactly (and in same order):
["YWJjZGVm=="]
The recorded OperationRequest no longer reflects the request that was actually sent, so any snippet generated from it will document an incorrect cookie value.
Scenario 2 — Cookie header without = (crash)
A Cookie header value without = is malformed, but it is reachable via WebTestClient's low-level header API:
ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null))
.configureClient()
.baseUrl("http://localhost")
.build()
.get()
.uri("/foo")
.header(HttpHeaders.COOKIE, "malformed")
.exchange()
.expectBody()
.returnResult();
this.converter.convert(result);
Verified locally on main (commit 631b6c22):
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at org.springframework.restdocs.webtestclient.WebTestClientRequestConverter.createRequestCookie(WebTestClientRequestConverter.java:109)
at org.springframework.restdocs.webtestclient.WebTestClientRequestConverter.extractCookies(WebTestClientRequestConverter.java:104)
at org.springframework.restdocs.webtestclient.WebTestClientRequestConverter.convert(WebTestClientRequestConverter.java:62)
This is admittedly an unusual input, but since REST Docs runs as part of a test suite, an ArrayIndexOutOfBoundsException originating inside the converter can be confusing to track down — the stack trace doesn't make it
obvious that the cookie header is the cause. Scenario 1 above is the more important case; I'm mentioning this one mainly because both have the same root cause and would naturally be fixed together.
Possible fix
One straightforward option would be to split on only the first = and handle the no-= case explicitly, e.g.:
private RequestCookie createRequestCookie(String header) {
int separator = header.indexOf('=');
if (separator == -1) {
return new RequestCookie(header, "");
}
return new RequestCookie(header.substring(0, separator), header.substring(separator + 1));
}
I'm happy to defer to whatever approach you prefer for the no-= case (empty string, skipping the cookie, or throwing a clearer error).
Environment
- Spring REST Docs:
main branch, commit 631b6c22
- Reproduced via two new tests in
WebTestClientRequestConverterTests, both of which fail against the current code at WebTestClientRequestConverter.java:109.
If this analysis looks right to you, I'd be glad to open a PR with a fix and regression tests for both scenarios.
Summary
WebTestClientRequestConverter.createRequestCookie(String)parses theCookieheader value with a naivesplit("=")and unconditionally accessescomponents[1]. This leads to two related problems:=(e.g. Base64-padded session IDs, JWTs in some encodings), only the substring before the first=is captured as the value, and the remainder is silentlydropped.
ArrayIndexOutOfBoundsException— when a cookie header value contains no=at all,components[1]throws.Affected code
spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java, lines 107–110:There is no length check on
components, andsplit("=")is called without a limit, so any=inside the value is also treated as a delimiter.For comparison,
MockMvcRequestConverter.extractCookies(...)reads cookies from the already-parsedMockHttpServletRequest#getCookies()and never touches the raw header string, so the equivalent path on the MockMvc side isunaffected.
Scenario 1 — Cookie value containing
=(data corruption)=is a valid character inside a cookie value per RFC 6265 §4.1.1 (only;,,, whitespace, and a few control characters are excluded). It is also extremelycommon in practice: Base64 padding (
abc=,abcd==) is the obvious example, and many session/JWT cookies end up containing it.This is reachable through the standard
WebTestClient.cookie(name, value)API:I verified this locally against
main(commit631b6c22); the assertion fails with:The recorded
OperationRequestno longer reflects the request that was actually sent, so any snippet generated from it will document an incorrect cookie value.Scenario 2 — Cookie header without
=(crash)A
Cookieheader value without=is malformed, but it is reachable viaWebTestClient's low-level header API:Verified locally on
main(commit631b6c22):This is admittedly an unusual input, but since REST Docs runs as part of a test suite, an
ArrayIndexOutOfBoundsExceptionoriginating inside the converter can be confusing to track down — the stack trace doesn't make itobvious that the cookie header is the cause. Scenario 1 above is the more important case; I'm mentioning this one mainly because both have the same root cause and would naturally be fixed together.
Possible fix
One straightforward option would be to split on only the first
=and handle the no-=case explicitly, e.g.:I'm happy to defer to whatever approach you prefer for the no-
=case (empty string, skipping the cookie, or throwing a clearer error).Environment
mainbranch, commit631b6c22WebTestClientRequestConverterTests, both of which fail against the current code atWebTestClientRequestConverter.java:109.If this analysis looks right to you, I'd be glad to open a PR with a fix and regression tests for both scenarios.