From 6fe904126fc544d4277180a3234010a78a82cf56 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Fri, 20 Feb 2026 08:59:36 +0200 Subject: [PATCH] Make Date header parsing locale-independent Parse RFC1123 Date headers with strptime_l using the classic locale and a literal GMT token, and trim header whitespace before parsing. Treat invalid/partial parses as missing timestamps and remove the extra GMT offset correction now that UTC parsing is explicit. Relates-To: HERESUP-57013 Signed-off-by: Mykhailo Diachenko Signed-off-by: Rustam Gamidov --- .../src/AuthenticationClientUtils.cpp | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/olp-cpp-sdk-authentication/src/AuthenticationClientUtils.cpp b/olp-cpp-sdk-authentication/src/AuthenticationClientUtils.cpp index 7de7dccca..ef0a9b464 100644 --- a/olp-cpp-sdk-authentication/src/AuthenticationClientUtils.cpp +++ b/olp-cpp-sdk-authentication/src/AuthenticationClientUtils.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -108,18 +109,37 @@ std::time_t ParseTime(const std::string& value) { #else +std::string TrimDateHeaderValue(const std::string& value) { + const auto begin = value.find_first_not_of(" \t\r\n"); + if (begin == std::string::npos) { + return {}; + } + const auto end = value.find_last_not_of(" \t\r\n"); + return value.substr(begin, end - begin + 1); +} + std::time_t ParseTime(const std::string& value) { std::tm tm = {}; - const auto format = "%a, %d %b %Y %H:%M:%S %Z"; - const auto parsed_until = ::strptime(value.c_str(), format, &tm); - if (parsed_until != value.c_str() + value.size()) { - OLP_SDK_LOG_WARNING(kLogTag, "Timestamp is not fully parsed" << value); + const auto trimmed_value = TrimDateHeaderValue(value); + + // Use a C locale to keep RFC1123 parsing locale-independent. + // Literal "GMT" avoids platform-specific %Z behaviour. + locale_t c_locale = newlocale(LC_ALL_MASK, "C", (locale_t)0); + if (c_locale == (locale_t)0) { + OLP_SDK_LOG_WARNING(kLogTag, "Failed to create C locale"); + return static_cast(-1); + } + + const auto parsed_until = ::strptime_l( + trimmed_value.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm, c_locale); + freelocale(c_locale); + + if (parsed_until != trimmed_value.c_str() + trimmed_value.size()) { + OLP_SDK_LOG_WARNING(kLogTag, "Timestamp is not fully parsed " << value); + return static_cast(-1); } - // MacOS updates `tm_isdst`, `tm_zone` and `tm_gmtoff` fields in `timegm` - // call. - const auto gmtoff = tm.tm_gmtoff; - const auto local_time = timegm(&tm); - return local_time - gmtoff; + + return timegm(&tm); } #endif @@ -133,7 +153,10 @@ porting::optional GetTimestampFromHeaders( obg.first, kDate); }); if (it != end(headers)) { - return ParseTime(it->second); + const auto timestamp = ParseTime(it->second); + if (timestamp != static_cast(-1)) { + return timestamp; + } } return porting::none; }