Skip to content

Commit 44a7e89

Browse files
authored
Ensure RFC 6265 compliance when sending multiple cookies (#2994)
According to RFC 6265 "When the user agent generates an HTTP request, the user agent MUST NOT attach more than one Cookie header field." https://datatracker.ietf.org/doc/html/rfc6265#section-5.4 Fixes #2983
1 parent 9fa10f8 commit 44a7e89

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

reactor-netty-http/src/main/java/reactor/netty/http/client/HttpClientOperations.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.net.URISyntaxException;
2121
import java.nio.channels.ClosedChannelException;
2222
import java.time.Duration;
23+
import java.util.ArrayList;
2324
import java.util.Collections;
25+
import java.util.List;
2426
import java.util.Map;
2527
import java.util.Objects;
2628
import java.util.Set;
@@ -106,6 +108,7 @@ class HttpClientOperations extends HttpOperations<NettyInbound, NettyOutbound>
106108
final HttpHeaders requestHeaders;
107109
final ClientCookieEncoder cookieEncoder;
108110
final ClientCookieDecoder cookieDecoder;
111+
final List<Cookie> cookieList;
109112
final Sinks.One<HttpHeaders> trailerHeaders;
110113

111114
Supplier<String>[] redirectedFrom = EMPTY_REDIRECTIONS;
@@ -144,6 +147,7 @@ class HttpClientOperations extends HttpOperations<NettyInbound, NettyOutbound>
144147
this.requestHeaders = replaced.requestHeaders;
145148
this.cookieEncoder = replaced.cookieEncoder;
146149
this.cookieDecoder = replaced.cookieDecoder;
150+
this.cookieList = replaced.cookieList;
147151
this.resourceUrl = replaced.resourceUrl;
148152
this.path = replaced.path;
149153
this.responseTimeout = replaced.responseTimeout;
@@ -165,14 +169,14 @@ class HttpClientOperations extends HttpOperations<NettyInbound, NettyOutbound>
165169
this.requestHeaders = nettyRequest.headers();
166170
this.cookieDecoder = decoder;
167171
this.cookieEncoder = encoder;
172+
this.cookieList = new ArrayList<>();
168173
this.trailerHeaders = Sinks.unsafe().one();
169174
}
170175

171176
@Override
172177
public HttpClientRequest addCookie(Cookie cookie) {
173178
if (!hasSentHeaders()) {
174-
this.requestHeaders.add(HttpHeaderNames.COOKIE,
175-
cookieEncoder.encode(cookie));
179+
this.cookieList.add(cookie);
176180
}
177181
else {
178182
throw new IllegalStateException("Status and headers already sent");
@@ -587,6 +591,10 @@ protected void afterMarkSentHeaders() {
587591

588592
@Override
589593
protected void beforeMarkSentHeaders() {
594+
if (!cookieList.isEmpty()) {
595+
requestHeaders.add(HttpHeaderNames.COOKIE, cookieEncoder.encode(cookieList));
596+
}
597+
590598
if (redirectedFrom.length > 0) {
591599
if (redirectRequestConsumer != null) {
592600
redirectRequestConsumer.accept(this);

reactor-netty-http/src/test/java/reactor/netty/http/HttpCookieHandlingTests.java

+36
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import java.time.Duration;
1919
import java.util.Collection;
20+
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Set;
2223
import java.util.function.Function;
2324

2425
import io.netty.handler.codec.http.HttpHeaderNames;
26+
import io.netty.handler.codec.http.HttpMethod;
2527
import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
2628
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
2729
import io.netty.handler.codec.http.cookie.Cookie;
@@ -164,4 +166,38 @@ private void doTestServerCookiesDecodingMultipleCookiesSameName(
164166
.expectComplete()
165167
.verify(Duration.ofSeconds(5));
166168
}
169+
170+
@Test
171+
void testIssue2983() {
172+
disposableServer =
173+
createServer()
174+
.handle((req, res) -> {
175+
List<String> cookies = req.requestHeaders().getAll(HttpHeaderNames.COOKIE);
176+
return cookies.size() == 1 ? res.sendString(Mono.just(cookies.get(0))) :
177+
res.sendString(Mono.just("ERROR"));
178+
})
179+
.bindNow();
180+
181+
createClient(disposableServer.port())
182+
.request(HttpMethod.GET)
183+
.uri("/")
184+
.send((req, out) -> {
185+
Cookie cookie1 = new DefaultCookie("testIssue2983_1", "1");
186+
cookie1.setPath("/");
187+
Cookie cookie2 = new DefaultCookie("testIssue2983_2", "2");
188+
cookie2.setPath("/2");
189+
Cookie cookie3 = new DefaultCookie("testIssue2983_3", "3");
190+
req.addCookie(cookie1)
191+
.addCookie(cookie2)
192+
.addCookie(cookie3);
193+
return out;
194+
})
195+
.responseContent()
196+
.aggregate()
197+
.asString()
198+
.as(StepVerifier::create)
199+
.expectNext("testIssue2983_3=3; testIssue2983_2=2; testIssue2983_1=1")
200+
.expectComplete()
201+
.verify(Duration.ofSeconds(5));
202+
}
167203
}

0 commit comments

Comments
 (0)