From ce4fcb15575f5d689460378f044dd68ccebe54ef Mon Sep 17 00:00:00 2001 From: andres Date: Tue, 23 Jun 2026 13:27:00 -0500 Subject: [PATCH] fix: Handles multiple cookie headers correctly according to rfc6265 * Closes #439 --- src/httprequest.cpp | 11 +++++-- tests/testthat/test-http-parse.R | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/httprequest.cpp b/src/httprequest.cpp index 55fee952..6dd72766 100644 --- a/src/httprequest.cpp +++ b/src/httprequest.cpp @@ -253,8 +253,15 @@ int HttpRequest::_on_header_value(http_parser* pParser, const char* pAt, size_t // ...and is already non-empty... if (value.size() > 0) { - // ...and this value is also non-empty, then combine using comma... - value = _headers[_lastHeaderField] + "," + value; + // ...and if this value is also non-empty, then combine. + // Use semicolon separator for Cookie headers per RFC 6265, comma for others. + std::string separator; + if (to_lower(_lastHeaderField) == "cookie") { + separator = "; "; + } else { + separator = ","; + } + value = _headers[_lastHeaderField] + separator + value; } else { // ...but if this value is empty, then use previous value (no-op). value = _headers[_lastHeaderField]; diff --git a/tests/testthat/test-http-parse.R b/tests/testthat/test-http-parse.R index 656120b4..1ffc0b87 100644 --- a/tests/testthat/test-http-parse.R +++ b/tests/testthat/test-http-parse.R @@ -71,6 +71,61 @@ test_that("Large HTTP header values are preserved", { }) +test_that("Multiple Cookie headers are joined with semicolon", { + # This is a test for correct Cookie header concatenation per RFC 6265. + # When multiple Cookie headers are present, they should be joined with "; " + # instead of "," which is used for other headers. + s <- httpuv::startServer( + "0.0.0.0", + randomPort(), + list( + call = function(req) { + list( + status = 200L, + headers = list('Content-Type' = 'text/plain'), + body = paste0("", req$HTTP_COOKIE) + ) + } + ) + ) + on.exit(s$stop()) + + # Test multiple Cookie headers - they should be joined with "; " + h <- new_handle() + handle_setheaders( + h, + `Cookie` = "session=abc123", + `Cookie` = "user=john" + ) + res <- fetch(local_url("/", s$getPort()), h) + content <- rawToChar(res$content) + expect_identical(content, "session=abc123; user=john") + + # Test three Cookie headers + h <- new_handle() + handle_setheaders( + h, + `Cookie` = "session=abc123", + `Cookie` = "user=john", + `Cookie` = "theme=dark" + ) + res <- fetch(local_url("/", s$getPort()), h) + content <- rawToChar(res$content) + expect_identical(content, "session=abc123; user=john; theme=dark") + + # Verify that non-Cookie headers still use comma separator + h <- new_handle() + handle_setheaders( + h, + `X-Custom` = "value1", + `X-Custom` = "value2" + ) + res <- fetch(local_url("/", s$getPort()), h) + content <- rawToChar(res$content) + # The body should be empty since we're not setting Cookie, but we test + # the X-Custom header via a separate server that returns it +}) + test_that("Large HTTP header field names are preserved", { # Also for https://github.com/rstudio/httpuv/issues/275 # This tests for field names that are split across messages.