From: Marcin Siodelski Date: Wed, 21 Dec 2016 19:58:08 +0000 (+0100) Subject: [5088] Implemented date time conversions for HTTP. X-Git-Tag: trac5090_base~2^2~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f95a200193a4a2138a57970258aeefc19225fd71;p=thirdparty%2Fkea.git [5088] Implemented date time conversions for HTTP. --- diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 90af21c983..50cda161c4 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -22,7 +22,8 @@ EXTRA_DIST = http_messages.mes CLEANFILES = *.gcno *.gcda http_messages.h http_messages.cc s-messages lib_LTLIBRARIES = libkea-http.la -libkea_http_la_SOURCES = http_log.cc http_log.h +libkea_http_la_SOURCES = date_time.cc date_time.h +libkea_http_la_SOURCES += http_log.cc http_log.h libkea_http_la_SOURCES += header_context.h libkea_http_la_SOURCES += http_types.h libkea_http_la_SOURCES += post_request.cc post_request.h @@ -34,7 +35,6 @@ libkea_http_la_SOURCES += response.cc response.h libkea_http_la_SOURCES += response_creator.h libkea_http_la_SOURCES += response_json.cc response_json.h - nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/src/lib/http/date_time.cc b/src/lib/http/date_time.cc new file mode 100644 index 0000000000..88bdfa509c --- /dev/null +++ b/src/lib/http/date_time.cc @@ -0,0 +1,137 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +using namespace boost::gregorian; +using namespace boost::local_time; +using namespace boost::posix_time; + +namespace isc { +namespace http { + +HttpDateTime::HttpDateTime() + : time_(boost::posix_time::microsec_clock::universal_time()) { +} + +HttpDateTime::HttpDateTime(const ptime& t) + : time_(t) { +} + +std::string +HttpDateTime::rfc1123Format() const { + return (toString("%a, %d %b %Y %H:%M:%S GMT", "RFC 1123")); +} + +std::string +HttpDateTime::rfc850Format() const { + return (toString("%A, %d-%b-%y %H:%M:%S GMT", "RFC 850")); +} + +std::string +HttpDateTime::asctimeFormat() const { + return (toString("%a %b %e %H:%M:%S %Y", "asctime")); +} + +HttpDateTime +HttpDateTime::fromRfc1123(const std::string& time_string) { + return (HttpDateTime(fromString(time_string, + "%a, %d %b %Y %H:%M:%S %ZP", + "RFC 1123"))); +} + +HttpDateTime +HttpDateTime::fromRfc850(const std::string& time_string) { + return (HttpDateTime(fromString(time_string, + "%A, %d-%b-%y %H:%M:%S %ZP", + "RFC 850"))); +} + +HttpDateTime +HttpDateTime::fromAsctime(const std::string& time_string) { + std::string time_string_copy(time_string); + boost::replace_all(time_string_copy, " ", " 0"); + return (HttpDateTime(fromString(time_string_copy, + "%a %b %d %H:%M:%S %Y", + "asctime", + false))); +} + +HttpDateTime +HttpDateTime::fromAny(const std::string& time_string) { + HttpDateTime date_time; + try { + date_time = fromRfc1123(time_string); + return (date_time); + } catch (...) { + ; + } + + try { + date_time = fromRfc850(time_string); + return (date_time); + } catch (...) { + ; + } + + try { + date_time = fromAsctime(time_string); + } catch (...) { + isc_throw(HttpTimeConversionError, + "unsupported time format of the '" << time_string + << "'"); + } + + return (date_time); + +} + +std::string +HttpDateTime::toString(const std::string& format, + const std::string& method_name) const { + std::ostringstream s; + time_facet* df(new time_facet(format.c_str())); + s.imbue(std::locale(s.getloc(), df)); + s << time_; + if (s.fail()) { + isc_throw(HttpTimeConversionError, "unable to convert " + << "time value of '" << time_ << "'" + << " to " << method_name << " format"); + } + return (s.str()); +} + + +ptime +HttpDateTime::fromString(const std::string& time_string, + const std::string& format, + const std::string& method_name, + const bool zone_check) { + std::istringstream s(time_string); + time_input_facet* tif(new time_input_facet(format)); + s.imbue(std::locale(s.getloc(), tif)); + + time_zone_ptr zone(new posix_time_zone("GMT")); + local_date_time ldt = local_microsec_clock::local_time(zone); + s >> ldt; + if (s.fail() || + (zone_check && (!ldt.zone() || ldt.zone()->std_zone_abbrev() != "GMT"))) { + isc_throw(HttpTimeConversionError, "unable to parse " + << method_name << " time value of '" + << time_string << "'"); + } + + return (ldt.local_time()); +} + + +} // namespace http +} // namespace isc diff --git a/src/lib/http/date_time.h b/src/lib/http/date_time.h new file mode 100644 index 0000000000..e6970b310f --- /dev/null +++ b/src/lib/http/date_time.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HTTP_DATE_TIME_H +#define HTTP_DATE_TIME_H + +#include +#include +#include + +namespace isc { +namespace http { + +class HttpTimeConversionError : public Exception { +public: + HttpTimeConversionError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +class HttpDateTime { +public: + + HttpDateTime(); + + explicit HttpDateTime(const boost::posix_time::ptime& t); + + boost::posix_time::ptime getPtime() const { + return (time_); + } + + std::string rfc1123Format() const; + + std::string rfc850Format() const; + + std::string asctimeFormat() const; + + static HttpDateTime fromRfc1123(const std::string& time_string); + + static HttpDateTime fromRfc850(const std::string& time_string); + + static HttpDateTime fromAsctime(const std::string& time_string); + + static HttpDateTime fromAny(const std::string& time_string); + +private: + + std::string toString(const std::string& format, + const std::string& method_name) const; + + static boost::posix_time::ptime + fromString(const std::string& time_string, const std::string& format, + const std::string& method_name, const bool zone_check = true); + + boost::posix_time::ptime time_; + +}; + +} // namespace http +} // namespace isc + +#endif diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc index f88f2197db..1b492cface 100644 --- a/src/lib/http/response.cc +++ b/src/lib/http/response.cc @@ -5,9 +5,11 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include -#include +#include +#include #include +using namespace boost::local_time; using namespace isc::http; namespace { @@ -80,12 +82,13 @@ HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) { std::string HttpResponse::getDateHeaderValue() const { - time_t rawtime; - struct tm * timeinfo; + local_date_time t(local_sec_clock::local_time(time_zone_ptr())); + std::stringstream s; + local_time_facet* lf(new local_time_facet("%a, %d %b %Y %H:%M:%S GMT")); + s.imbue(std::locale(s.getloc(), lf)); + s << t; - time ( &rawtime ); - timeinfo = localtime ( &rawtime ); - return (std::string(asctime(timeinfo))); + return (s.str()); } std::string diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index 6a0b8e14e7..706065d130 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -20,9 +20,11 @@ TESTS = if HAVE_GTEST TESTS += libhttp_unittests -libhttp_unittests_SOURCES = post_request_json_unittests.cc +libhttp_unittests_SOURCES = date_time_unittests.cc +libhttp_unittests_SOURCES += post_request_json_unittests.cc libhttp_unittests_SOURCES += request_parser_unittests.cc libhttp_unittests_SOURCES += request_test.h +libhttp_unittests_SOURCES += response_test.h libhttp_unittests_SOURCES += request_unittests.cc libhttp_unittests_SOURCES += response_unittests.cc libhttp_unittests_SOURCES += response_json_unittests.cc diff --git a/src/lib/http/tests/date_time_unittests.cc b/src/lib/http/tests/date_time_unittests.cc new file mode 100644 index 0000000000..af8186dcdb --- /dev/null +++ b/src/lib/http/tests/date_time_unittests.cc @@ -0,0 +1,162 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include + + +#include + +using namespace boost::gregorian; +using namespace boost::posix_time; +using namespace isc::http; + +namespace { + +class HttpDateTimeTest : public ::testing::Test { +public: + + void testDateTime(const unsigned short exp_day_of_week, + const unsigned short exp_day, + const unsigned short exp_month, + const unsigned short exp_year, + const long exp_hours, + const long exp_minutes, + const long exp_seconds) { + ptime as_ptime = date_time_.getPtime(); + date date_part = as_ptime.date(); + + greg_weekday day_of_week = date_part.day_of_week(); + EXPECT_EQ(exp_day_of_week, day_of_week.as_number()); + + greg_day day = date_part.day(); + EXPECT_EQ(exp_day, day.as_number()); + + greg_month month = date_part.month(); + EXPECT_EQ(exp_month, month.as_number()); + + greg_year year = date_part.year(); + EXPECT_EQ(exp_year, static_cast(year)); + + time_duration time_of_day = as_ptime.time_of_day(); + EXPECT_EQ(exp_hours, time_of_day.hours()); + EXPECT_EQ(exp_minutes, time_of_day.minutes()); + EXPECT_EQ(exp_seconds, time_of_day.seconds()); + } + + HttpDateTime date_time_; + +}; + +TEST_F(HttpDateTimeTest, rfc1123Format) { + date gdate(greg_year(2002), greg_month(1), greg_day(20)); + time_duration tm(23, 59, 59, 0); + ptime t = ptime(gdate, tm); + HttpDateTime date_time(t); + std::string formatted; + ASSERT_NO_THROW(formatted = date_time.rfc1123Format()); + EXPECT_EQ("Sun, 20 Jan 2002 23:59:59 GMT", formatted); +} + +TEST_F(HttpDateTimeTest, rfc850Format) { + date gdate(greg_year(1994), greg_month(8), greg_day(6)); + time_duration tm(11, 12, 13, 0); + ptime t = ptime(gdate, tm); + + HttpDateTime date_time(t); + std::string formatted; + ASSERT_NO_THROW(formatted = date_time.rfc850Format()); + EXPECT_EQ("Saturday, 06-Aug-94 11:12:13 GMT", formatted); +} + +TEST_F(HttpDateTimeTest, asctimeFormat) { + date gdate(greg_year(1999), greg_month(11), greg_day(2)); + time_duration tm(03, 57, 12, 0); + ptime t = ptime(gdate, tm); + + HttpDateTime date_time(t); + std::string formatted; + ASSERT_NO_THROW(formatted = date_time.asctimeFormat()); + EXPECT_EQ("Tue Nov 2 03:57:12 1999", formatted); +} + +TEST_F(HttpDateTimeTest, fromRfc1123) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45 GMT") + ); + testDateTime(3, 21, 12, 2016, 18, 53, 45); + EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dex 2016 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 43 Dec 2016 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 16 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 1853:45 GMT"), + HttpTimeConversionError); +} + +TEST_F(HttpDateTimeTest, fromRfc850) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 18:53:45 GMT"); + ); + testDateTime(3, 21, 12, 2016, 18, 53, 45); + EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 55-Dec-16 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dex-16 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-2016 18:53:45 GMT"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 1853:45 GMT"), + HttpTimeConversionError); +} + +TEST_F(HttpDateTimeTest, fromRfcAsctime) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 2016"); + ); + testDateTime(3, 21, 12, 2016, 8, 49, 37); + EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dex 21 08:49:37 2016"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 55 08:49:37 2016"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 16"), + HttpTimeConversionError); + EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:4937 2016"), + HttpTimeConversionError); +} + +TEST_F(HttpDateTimeTest, fromAnyRfc1123) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromAny("Thu, 05 Jan 2017 09:15:06 GMT"); + ); + testDateTime(4, 5, 1, 2017, 9, 15, 06); +} + +TEST_F(HttpDateTimeTest, fromAnyRfc850) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromAny("Saturday, 18-Feb-17 01:02:10 GMT"); + ); + testDateTime(6, 18, 2, 2017, 1, 2, 10); +} + +TEST_F(HttpDateTimeTest, fromAnyAsctime) { + ASSERT_NO_THROW( + date_time_ = HttpDateTime::fromAny("Wed Mar 1 15:45:07 2017 GMT"); + ); + testDateTime(3, 1, 3, 2017, 15, 45, 7); +} + +TEST_F(HttpDateTimeTest, fromAnyInvalidFormat) { + EXPECT_THROW(HttpDateTime::fromAsctime("20020131T235959"), + HttpTimeConversionError); +} + +} diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc index 553992a19b..6bde0be15d 100644 --- a/src/lib/http/tests/response_json_unittests.cc +++ b/src/lib/http/tests/response_json_unittests.cc @@ -7,14 +7,18 @@ #include #include #include +#include #include #include using namespace isc::data; using namespace isc::http; +using namespace isc::http::test; namespace { +typedef TestHttpResponseBase TestHttpResponseJson; + class HttpResponseJsonTest : public ::testing::Test { public: @@ -40,7 +44,7 @@ public: void testGenericResponse(const HttpStatusCode& status_code, const std::string& status_message) { - HttpResponseJson response(HttpVersion(1, 0), status_code); + TestHttpResponseJson response(HttpVersion(1, 0), status_code); std::ostringstream status_message_json; status_message_json << "{ \"result\": " << static_cast(status_code) @@ -50,7 +54,8 @@ public: response_string << "HTTP/1.0 " << static_cast(status_code) << " " << status_message << "\r\n" - "Content-Type: application/json\r\n"; + "Content-Type: application/json\r\n" + "Date: " << response.getDateHeaderValue() << "\r\n"; if (HttpResponse::isClientError(status_code) || HttpResponse::isServerError(status_code)) { @@ -75,13 +80,14 @@ public: }; TEST_F(HttpResponseJsonTest, responseWithContent) { - HttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK); + TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK); ASSERT_NO_THROW(response.setBodyAsJson(json_)); std::ostringstream response_string; response_string << "HTTP/1.1 200 OK\r\n" "Content-Type: application/json\r\n" + "Date: " << response.getDateHeaderValue() << "\r\n" "Content-Length: " << json_string_from_json_.length() << "\r\n\r\n" << json_string_from_json_; EXPECT_EQ(response_string.str(), response.toString()); diff --git a/src/lib/http/tests/response_test.h b/src/lib/http/tests/response_test.h new file mode 100644 index 0000000000..fa92368547 --- /dev/null +++ b/src/lib/http/tests/response_test.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HTTP_RESPONSE_TEST_H +#define HTTP_RESPONSE_TEST_H + +namespace isc { +namespace http { +namespace test { + +template +class TestHttpResponseBase : public HttpResponseType { +public: + + TestHttpResponseBase(const HttpVersion& version, const HttpStatusCode& status_code) + : HttpResponseType(version, status_code) { + } + + virtual std::string getDateHeaderValue() const { + return ("Tue, 19 Dec 2016 18:53:35 GMT"); + } + + std::string generateDateHeaderValue() const { + return (HttpResponseType::getDateHeaderValue()); + } +}; + +} // namespace test +} // namespace http +} // namespace isc + +#endif diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc index a10b004b67..9da562b9a4 100644 --- a/src/lib/http/tests/response_unittests.cc +++ b/src/lib/http/tests/response_unittests.cc @@ -5,39 +5,34 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include +#include #include #include #include +using namespace boost::posix_time; using namespace isc::http; +using namespace isc::http::test; namespace { -class TestHttpResponse : public HttpResponse { -public: - - TestHttpResponse(const HttpVersion& version, const HttpStatusCode& status_code) - : HttpResponse(version, status_code) { - } - - virtual std::string getDateHeaderValue() const { - return ("Mon Dec 19 18:53:35 2016"); - } -}; +typedef TestHttpResponseBase TestHttpResponse; class HttpResponseTest : public ::testing::Test { public: void testResponse(const HttpStatusCode& status_code, const std::string& status_message) { - HttpResponse response(HttpVersion(1, 0), status_code); + TestHttpResponse response(HttpVersion(1, 0), status_code); response.addHeader("Content-Type", "text/html"); std::ostringstream response_string; response_string << "HTTP/1.0 " << static_cast(status_code) << " " << status_message << "\r\n" - << "Content-Type: text/html\r\n\r\n"; + << "Content-Type: text/html\r\n" + << "Date: " << response.getDateHeaderValue() << "\r\n\r\n"; EXPECT_EQ(response_string.str(), response.toString()); } @@ -51,7 +46,7 @@ TEST_F(HttpResponseTest, responseOK) { "

Some header

" ""; - HttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK); + TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK); response.addHeader("Content-Type", "text/html"); response.addHeader("Host", "kea.example.org"); response.setBody(sample_body); @@ -61,6 +56,7 @@ TEST_F(HttpResponseTest, responseOK) { "HTTP/1.0 200 OK\r\n" "Content-Type: text/html\r\n" "Host: kea.example.org\r\n" + "Date: " << response.getDateHeaderValue() << "\r\n" "Content-Length: " << sample_body.length() << "\r\n\r\n" << sample_body; EXPECT_EQ(response_string.str(), response.toString()); @@ -123,4 +119,13 @@ TEST_F(HttpResponseTest, isServerError) { EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::SERVICE_UNAVAILABLE)); } +TEST_F(HttpResponseTest, getDateHeaderValue) { + TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK); + std::string generated_date = response.generateDateHeaderValue(); + HttpDateTime parsed_time = HttpDateTime::fromRfc1123(generated_date); + time_duration parsed_to_current = + microsec_clock::universal_time() - parsed_time.getPtime(); + EXPECT_LT(parsed_to_current.seconds(), 10); +} + }