Skip to content
Merged
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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# httpuv (development version)

* Fixed #439: Multiple `Cookie` request headers are now combined with `"; "` instead of `","`, as required by RFC 6265 (thanks to @andyquinterom, #440).

# httpuv 1.6.17

* Closed #426: Uses native symbol registration for calls into compiled code, resulting in performance gains from not having to perform a lookup on each call. (#427)
Expand Down
11 changes: 9 additions & 2 deletions src/httprequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
69 changes: 69 additions & 0 deletions tests/testthat/test-http-parse.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,75 @@ 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.
headers_received <- NULL
s <- httpuv::startServer(
"0.0.0.0",
randomPort(),
list(
call = function(req) {
# Save the headers for examination later
headers_received <<- req$HEADERS
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")

# Cookie headers are combined even when another header sits between them,
# and regardless of the case used for the field name.
h <- new_handle()
handle_setheaders(
h,
`Cookie` = "session=abc123",
`X-Custom` = "value1",
`cookie` = "user=john"
)
res <- fetch(local_url("/", s$getPort()), h)
content <- rawToChar(res$content)
expect_identical(content, "session=abc123; user=john")

# Verify that non-Cookie headers still use comma separator
h <- new_handle()
handle_setheaders(
h,
`X-Custom` = "value1",
`X-Custom` = "value2"
)
fetch(local_url("/", s$getPort()), h)
expect_identical(headers_received[["x-custom"]], "value1,value2")
})

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.
Expand Down
Loading