Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public MutableHttpRequest<B> cookies(Set<Cookie> cookies) {
String value = ClientCookieEncoder.INSTANCE.encode(cookie);
this.cookies.put(cookie.getName(), value);
}
headers.set(HttpHeaderNames.COOKIE, String.join(";", this.cookies.values()));
headers.set(HttpHeaderNames.COOKIE, String.join("; ", this.cookies.values()));
} else if (!cookies.isEmpty()) {
cookie(cookies.iterator().next());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,20 @@ public MutableHttpRequest<B> cookie(Cookie cookie) {

private void updateCookies() {
headers.remove(HttpHeaders.COOKIE);
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Cookie cookie : cookies.getAll()) {
headers.add(HttpHeaders.COOKIE, ClientCookieEncoder.INSTANCE.encode(cookie));
String encoded = ClientCookieEncoder.INSTANCE.encode(cookie);
if (encoded != null && !encoded.isEmpty()) {
if (!first) {
sb.append("; ");
}
sb.append(encoded);
first = false;
}
}
if (sb.length() > 0) {
headers.set(HttpHeaders.COOKIE, sb.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.micronaut.repro.http

import io.micronaut.http.HttpHeaders
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.client.netty.NettyClientHttpRequestFactory
import io.micronaut.http.cookie.Cookie
import io.micronaut.http.simple.SimpleHttpRequestFactory
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class CookieHeaderSingleValueReproTest {

private val cookies = linkedSetOf(
Cookie.of("a", "1"),
Cookie.of("b", "2"),
Cookie.of("a", "3") // latest value for 'a' should win
)

// Simple request factory
@Test
fun simpleRequest_addCookies_oneByOne_resultsInSingleCookieHeader() {
val request: MutableHttpRequest<Any> = SimpleHttpRequestFactory().get<Any>("/")
assertSingleCookieHeaderAfterAddingCookiesIndividually(request)
}

@Test
fun simpleRequest_addCookies_bulk_resultsInSingleCookieHeader() {
val request: MutableHttpRequest<Any> = SimpleHttpRequestFactory().get<Any>("/")
assertSingleCookieHeaderAfterAddingCookiesBulk(request)
}

// Netty client request factory
@Test
fun nettyClientRequest_addCookies_oneByOne_resultsInSingleCookieHeader() {
val request: MutableHttpRequest<Any> = NettyClientHttpRequestFactory().get<Any>("/")
assertSingleCookieHeaderAfterAddingCookiesIndividually(request)
}

@Test
fun nettyClientRequest_addCookies_bulk_resultsInSingleCookieHeader() {
val request: MutableHttpRequest<Any> = NettyClientHttpRequestFactory().get<Any>("/")
assertSingleCookieHeaderAfterAddingCookiesBulk(request)
}

private fun assertSingleCookieHeaderAfterAddingCookiesIndividually(request: MutableHttpRequest<*>) {
cookies.forEach { request.cookie(it) }
assertSingleCookieHeaderAndValues(request)
}

private fun assertSingleCookieHeaderAfterAddingCookiesBulk(request: MutableHttpRequest<*>) {
request.cookies(cookies)
assertSingleCookieHeaderAndValues(request)
}

private fun assertSingleCookieHeaderAndValues(request: MutableHttpRequest<*>) {
val cookieHeaders = request.headers.getAll(HttpHeaders.COOKIE)
assertEquals(1, cookieHeaders.size, "Expected a single Cookie header value")

val value = cookieHeaders[0]
// Validate semantics (latest value wins, all expected cookies present)
val parts = value.split(";".toRegex()).map { it.trim() }.filter { it.isNotEmpty() }
assertTrue(parts.contains("a=3"), "Expected latest value for cookie 'a' to be present")
assertTrue(parts.contains("b=2"), "Expected cookie 'b' to be present")
assertFalse(parts.contains("a=1"), "Expected old value for cookie 'a' to be replaced")
}
}