Skip to content

Commit

Permalink
Added additional formats for getHttpDate function and fixed undefined…
Browse files Browse the repository at this point in the history
… behavior upon error (#453) (#456)

This patch adds support for the RFC 850 and asctime format. If an error
occurs, we now return a date with the epoch value of -1 and warn instead of
triggering undefined behavior. This is checked by a new set of tests.

Co-authored-by: VayuDev <vayudev@protonmail.com>
Co-authored-by: antao <antao2002@gmail.com>
  • Loading branch information
3 people committed Jun 5, 2020
1 parent 5faab6b commit d4d5adf
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 7 deletions.
5 changes: 3 additions & 2 deletions lib/inc/drogon/Cookie.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
*/
#pragma once

#include <string>
#include <trantor/utils/Date.h>
#include <string>
#include <limits>

namespace drogon
{
Expand Down Expand Up @@ -235,7 +236,7 @@ class Cookie
}

private:
trantor::Date expiresDate_;
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
bool httpOnly_{true};
bool secure_{false};
std::string domain_;
Expand Down
4 changes: 4 additions & 0 deletions lib/inc/drogon/utils/Utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <string>
#include <vector>
#include <set>
#include <limits>
#ifdef _WIN32
#include <time.h>
char *strptime(const char *s, const char *f, struct tm *tm);
Expand Down Expand Up @@ -125,6 +126,9 @@ std::string brotliDecompress(const char *data, const size_t ndata);
char *getHttpFullDate(const trantor::Date &date = trantor::Date::now());

/// Get the trantor::Date object according to the http full date string
/**
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
*/
trantor::Date getHttpDate(const std::string &httpFullDateString);

/// Get a formatted string
Expand Down
4 changes: 3 additions & 1 deletion lib/src/Cookie.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ std::string Cookie::cookieString() const
{
std::string ret = "Set-Cookie: ";
ret.append(key_).append("= ").append(value_).append("; ");
if (expiresDate_.microSecondsSinceEpoch() > 0)
if (expiresDate_.microSecondsSinceEpoch() !=
(std::numeric_limits<int64_t>::max)() &&
expiresDate_.microSecondsSinceEpoch() >= 0)
{
ret.append("Expires= ")
.append(utils::getHttpFullDate(expiresDate_))
Expand Down
24 changes: 21 additions & 3 deletions lib/src/Utilities.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <string>
#include <thread>
#include <algorithm>
#include <array>
#include <cctype>
#include <cstdlib>
#include <stdio.h>
Expand Down Expand Up @@ -935,10 +936,27 @@ char *getHttpFullDate(const trantor::Date &date)
}
trantor::Date getHttpDate(const std::string &httpFullDateString)
{
static const std::array<const char *, 4> formats = {
// RFC822 (default)
"%a, %d %b %Y %H:%M:%S",
// RFC 850 (deprecated)
"%a, %d-%b-%y %H:%M:%S",
// ansi asctime format
"%a %b %d %H:%M:%S %Y",
// weird RFC 850-hybrid thing that reddit uses
"%a, %d-%b-%Y %H:%M:%S",
};
struct tm tmptm;
strptime(httpFullDateString.c_str(), "%a, %d %b %Y %H:%M:%S", &tmptm);
auto epoch = timegm(&tmptm);
return trantor::Date(epoch * MICRO_SECONDS_PRE_SEC);
for (const char *format : formats)
{
if (strptime(httpFullDateString.c_str(), format, &tmptm) != NULL)
{
auto epoch = timegm(&tmptm);
return trantor::Date(epoch * MICRO_SECONDS_PRE_SEC);
}
}
LOG_WARN << "invalid datetime format: '" << httpFullDateString << "'";
return trantor::Date((std::numeric_limits<int64_t>::max)());
}
std::string formattedString(const char *format, ...)
{
Expand Down
4 changes: 3 additions & 1 deletion unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_executable(sha1_unittest SHA1Unittest.cpp ../lib/src/ssl_funcs/Sha1.cc)
add_executable(ostringstream_unittest OStringStreamUnitttest.cpp)
add_executable(base64_unittest Base64Unittest.cpp)
add_executable(pubsubservice_unittest PubSubServiceUnittest.cpp)
add_executable(httpdate_unittest HttpDateUnittest.cpp)
if(Brotli_FOUND)
add_executable(brotli_unittest BrotliUnittest.cpp)
endif()
Expand All @@ -18,7 +19,8 @@ set(UNITTEST_TARGETS
sha1_unittest
ostringstream_unittest
base64_unittest
pubsubservice_unittest)
pubsubservice_unittest
httpdate_unittest)
if(Brotli_FOUND)
set(UNITTEST_TARGETS ${UNITTEST_TARGETS} brotli_unittest)
endif()
Expand Down
35 changes: 35 additions & 0 deletions unittest/HttpDateUnittest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <drogon/utils/Utilities.h>
#include <gtest/gtest.h>
using namespace drogon;

TEST(HttpDate, rfc850)
{
auto date = utils::getHttpDate("Fri, 05-Jun-20 09:19:38 GMT");
EXPECT_EQ(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC, 1591348778);
}

TEST(HttpDate, redditFormat)
{
auto date = utils::getHttpDate("Fri, 05-Jun-2020 09:19:38 GMT");
EXPECT_EQ(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC, 1591348778);
}

TEST(HttpDate, invalidFormat)
{
auto date = utils::getHttpDate("Fri, this format is invalid");
EXPECT_EQ(date.microSecondsSinceEpoch(), (std::numeric_limits<int64_t>::max)());
}

TEST(HttpDate, asctimeFormat)
{
auto epoch = time(nullptr);
auto str = asctime(gmtime(&epoch));
auto date = utils::getHttpDate(str);
EXPECT_EQ(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC, epoch);
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

0 comments on commit d4d5adf

Please sign in to comment.