Skip to content

fix: make HTTP date formatting locale-independent#2486

Merged
an-tao merged 3 commits intodrogonframework:masterfrom
Greisby:fix/date_header
Apr 9, 2026
Merged

fix: make HTTP date formatting locale-independent#2486
an-tao merged 3 commits intodrogonframework:masterfrom
Greisby:fix/date_header

Conversation

@Greisby
Copy link
Copy Markdown
Contributor

@Greisby Greisby commented Apr 8, 2026

This PR follows up on the locale-related issue previously addressed in drogon PR #2217, which did not cover all runtime paths.

Some response rendering paths still call getHttpFullDate(), which relies on trantor::Date::toCustomFormattedString() (strftime, process-locale dependent).
As a result, the HTTP Date header can still be localized under non-C locales (I ran into this issue with a Date: header under a German locale).

This PR makes all Drogon's HTTP date generation utilities locale-independent by routing both getHttpFullDate() and getHttpFullDateStr() through a single static dedicated formatter (formatHttpDate, which uses fixed English weekday/month names and snprintf.
This also avoids stream/locale formatting overhead in this hot path.


Notes

  • drogon::utils::getHttpDate() remains locale-sensitive and may fail under non-C / non-English locales.
    There is currently no locale-safe HTTP date parser in Drogon.
    A robust fix would be to add a dedicated locale-independent parser, potentially adapted from proven implementations such as nginx or libcurl.
  • If trantor::Date::toCustomFormattedString() is also supposed to be locale-sensitive, then the proper long-term fix should be to fix the strftime usage in trantor itself.
    In that case, it would be preferable to simplify drogon by reverting the workaround-style changes introduced in these two PRs and relying on the corrected trantor behavior as the single source of truth.

@Greisby Greisby marked this pull request as ready for review April 8, 2026 10:44
@Greisby Greisby requested a review from Copilot April 8, 2026 10:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes Drogon’s HTTP-date formatting locale-independent so Date: response headers (and cookie Expires= values) stay RFC-compliant under non-C locales by bypassing strftime/stream locale paths.

Changes:

  • Added a dedicated formatHttpDate() formatter that uses fixed English weekday/month names + snprintf.
  • Updated getHttpFullDate() and getHttpFullDateStr() to route through the dedicated formatter (reducing locale and stream overhead).
  • Removed unused code in dateToCustomFormattedString().
Comments suppressed due to low confidence (1)

lib/src/Utilities.cc:1063

  • lastSecond is initialized to 0, so the very first call with a date that falls exactly on Unix epoch second 0 (or any value equal to the initializer) will hit the cache path and return an uninitialized/empty lastTimeString without formatting. Consider initializing lastSecond to a sentinel that cannot be a real timestamp (e.g., std::numeric_limits<int64_t>::min()) so the first call always formats.
char *getHttpFullDate(const trantor::Date &date)
{
    static thread_local int64_t lastSecond = 0;
    static thread_local char lastTimeString[128] = {0};
    auto nowSecond =
        date.microSecondsSinceEpoch() / trantor::Date::MICRO_SECONDS_PER_SEC;
    if (nowSecond == lastSecond)
    {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1082 to 1091
const std::string &getHttpFullDateStr(const trantor::Date &date)
{
static thread_local int64_t lastSecond = 0;
static thread_local std::string lastTimeString(128, 0);
static thread_local std::string lastTimeString;
auto nowSecond =
date.microSecondsSinceEpoch() / trantor::Date::MICRO_SECONDS_PER_SEC;
if (nowSecond == lastSecond)
{
return lastTimeString;
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same caching initializer issue as getHttpFullDate(): lastSecond starts at 0, so the first call where nowSecond == 0 (e.g., formatting Thu, 01 Jan 1970 00:00:00 GMT, commonly used for cookie deletion) returns lastTimeString without formatting. Initialize lastSecond to a sentinel value (e.g., std::numeric_limits<int64_t>::min()) so the first call always formats.

Copilot uses AI. Check for mistakes.
@Greisby Greisby requested a review from an-tao April 8, 2026 11:31
@an-tao an-tao merged commit 595ee83 into drogonframework:master Apr 9, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants