From 99a394fb4c41614cba1030870761cdb4c495a71c Mon Sep 17 00:00:00 2001 From: chuuminggg Date: Fri, 16 Oct 2026 09:46:17 +0900 Subject: [PATCH] Handle Cookie headers with multiple cookies or a cookie without = Previously, WebTestClientRequestConverter converted a Cookie header containing multiple cookies into a single cookie and failed with a StringIndexOutOfBoundsException when a cookie had no =. Each header value is now split on '; ' and a cookie without = is converted to a cookie with an empty value. Signed-off-by: chuuminggg See gh-1076 --- .../WebTestClientRequestConverter.java | 15 +++++-- .../WebTestClientRequestConverterTests.java | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java index db96acba9..007b15aca 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; @@ -89,12 +90,18 @@ private Collection extractCookies(HttpHeaders headers) { return Collections.emptyList(); } headers.remove(HttpHeaders.COOKIE); - return cookieHeaders.stream().map(this::createRequestCookie).collect(Collectors.toList()); + return cookieHeaders.stream() + .flatMap((header) -> Stream.of(header.split("; "))) + .map(this::createRequestCookie) + .collect(Collectors.toList()); } - private RequestCookie createRequestCookie(String header) { - int separator = header.indexOf('='); - return new RequestCookie(header.substring(0, separator), header.substring(separator + 1)); + private RequestCookie createRequestCookie(String cookie) { + int separator = cookie.indexOf('='); + if (separator == -1) { + return new RequestCookie(cookie, ""); + } + return new RequestCookie(cookie.substring(0, separator), cookie.substring(separator + 1)); } private final class ExchangeResultReactiveHttpInputMessage implements ReactiveHttpInputMessage { diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java index 705f34531..e7a807e46 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java @@ -350,4 +350,43 @@ void requestWithCookieValueContainingEqualsSign() { assertThat(request.getCookies()).extracting("value").containsExactly("YWJjZGVm=="); } + @Test + void requestWithCookieHeaderContainingMultipleCookies() { + ExchangeResult result = WebTestClient + .bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> ServerResponse.ok().build())) + .configureClient() + .baseUrl("http://localhost") + .build() + .get() + .uri("/foo") + .header(HttpHeaders.COOKIE, "cookieName1=cookieVal1; cookieName2=cookieVal2") + .exchange() + .expectBody() + .returnResult(); + OperationRequest request = this.converter.convert(result); + assertThat(request.getCookies()).hasSize(2); + assertThat(request.getHeaders().get(HttpHeaders.COOKIE)).isNull(); + assertThat(request.getCookies()).extracting("name").containsExactly("cookieName1", "cookieName2"); + assertThat(request.getCookies()).extracting("value").containsExactly("cookieVal1", "cookieVal2"); + } + + @Test + void requestWithCookieHeaderWithoutEqualsSign() { + ExchangeResult result = WebTestClient + .bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> ServerResponse.ok().build())) + .configureClient() + .baseUrl("http://localhost") + .build() + .get() + .uri("/foo") + .header(HttpHeaders.COOKIE, "cookieName") + .exchange() + .expectBody() + .returnResult(); + OperationRequest request = this.converter.convert(result); + assertThat(request.getCookies()).hasSize(1); + assertThat(request.getCookies()).extracting("name").containsExactly("cookieName"); + assertThat(request.getCookies()).extracting("value").containsExactly(""); + } + }