httpuv emits duplicate 'Date' response header when one is already supplied

Open Beginner friendly
#447 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
64/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
cpp, r

Research direction

Read src/httpresponse.h and the header handling in src/webapplication.cpp, starting with listToResponse() and its use of addHeader() and setHeader(). The issue also notes a separate static path for custom headers, which may need investigation. Reproduce with the provided R example and curl; done means the response has exactly one valid Date field whether the application supplies one or omits it.

Written by the indexing model from the issue text.

Description

Description

When an application response already includes a Date header, httpuv 1.6.17 sends a second Date field line, instead of preserving or replacing the supplied value.

Impact

RFC 9110 Sections 5.3 and 6.6.1 make Date a single HTTP-date field, not a list. Because Date is not list-valued, combining repeated lines with commas would produce a value that does not match the Date grammar. Clients, caches, or intermediaries may therefore reject or ignore the timestamp. This is an interoperability/conformance issue rather than an immediate security issue.

If the two dates differ, recipients that recover by selecting one field line may disagree about when the response originated; caches also use Date when calculating response age, so this can lead to inconsistent freshness handling.

Background

We found this while inspecting the raw response headers from our application during a cache-policy review. The application uses Plumber2, which runs on Fiery and uses reqres for request/response handling. The first Date is added by reqres 1.2.0 when it formats the response; our route does not set it. httpuv then adds a second Date to the wire response.

I searched the httpuv, Fiery, reqres, and Plumber2 issue trackers for matching reports and did not find one.

We reduced the behavior to the httpuv-only example below to isolate the HTTP server layer.

Environment

  • R 4.6.2
  • httpuv 1.6.17

Steps to reproduce

Save this as date-header-mwe.R:

message("Starting server at http://127.0.0.1:8081/ (press Ctrl+C to stop)")

httpuv::runServer(
	host = "127.0.0.1",
	port = 8081,
	app = list(
		call = function(req) {
			list(
				status = 200L,
				headers = list(
					# simulate an application-supplied Date. httpuv will add another Date automatically.
					Date = format(Sys.time(), "%a, %d %b %Y %H:%M:%S GMT", tz = "GMT")
				),
				body = ""
			)
		}
	)
)

Start the server:

Rscript date-header-mwe.R

In another process, send a request to the server:

curl -sS -D - -o /dev/null http://127.0.0.1:8081/

Actual behavior

HTTP/1.1 200 OK
Date: Thu, 05 Nov 2026 17:01:29 GMT
Date: Thu, 05 Nov 2026 17:01:29 GMT
Content-Length: 0

The date and header order may vary, but the response contains two Date field lines.

Expected behavior

HTTP/1.1 200 OK
Date: Thu, 05 Nov 2026 17:01:29 GMT
Content-Length: 0

The response should contain one valid Date field line. The server may use its own timestamp or an application-supplied timestamp, but should not emit both.

Possible patch

I'm personally new to httpuv, but to be as solution-oriented as possible, here's an AI-generated patch, which may fix this issue...

In v1.6.17, HttpResponse adds a default Date in its constructor (src/httpresponse.h). The listToResponse() path in src/webapplication.cpp then appends each R-supplied response header with addHeader(). For a supplied Date, using the existing case-insensitive setHeader() method instead would replace the default while preserving append behavior for other headers.

Illustrative patch for listToResponse() (a regression test should cover supplied and omitted Date):

diff --git a/src/webapplication.cpp b/src/webapplication.cpp
--- a/src/webapplication.cpp
+++ b/src/webapplication.cpp
@@
-    pResp->addHeader(
-      std::string((char*)headerNames[i], headerNames[i].size()),
-      Rcpp::as<std::string>(responseHeaders[i]));
+    const std::string name((char*)headerNames[i], headerNames[i].size());
+    const std::string value = Rcpp::as<std::string>(responseHeaders[i]);
+    if (to_lower(name) == "date") {
+      pResp->setHeader(name, value);
+    } else {
+      pResp->addHeader(name, value);
+    }

This covers the R response path shown above. Static-path custom headers are added through a separate code path and would need the same Date replacement if they are intended to support user-supplied Date values too.

Dominant language
C
Stars
252
Forks
87
PR merge metrics
No merged PRs in 30d

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rstudio/httpuv

All issues in rstudio/httpuv

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.