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
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)
--- /dev/null
+// 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 <http/date_time.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/date_time/date_facet.hpp>
+#include <boost/date_time/time_facet.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <sstream>
+
+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
--- /dev/null
+// 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 <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <http/response.h>
-#include <ctime>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/time_facet.hpp>
#include <sstream>
+using namespace boost::local_time;
using namespace isc::http;
namespace {
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
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
--- /dev/null
+// 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 <config.h>
+#include <http/date_time.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+
+
+#include <gtest/gtest.h>
+
+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<unsigned short>(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);
+}
+
+}
#include <config.h>
#include <cc/data.h>
#include <http/response_json.h>
+#include <http/tests/response_test.h>
#include <gtest/gtest.h>
#include <sstream>
using namespace isc::data;
using namespace isc::http;
+using namespace isc::http::test;
namespace {
+typedef TestHttpResponseBase<HttpResponseJson> TestHttpResponseJson;
+
class HttpResponseJsonTest : public ::testing::Test {
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<uint16_t>(status_code)
response_string <<
"HTTP/1.0 " << static_cast<uint16_t>(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)) {
};
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());
--- /dev/null
+// 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<typename HttpResponseType>
+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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
+#include <http/date_time.h>
#include <http/http_types.h>
#include <http/response.h>
+#include <http/tests/response_test.h>
#include <gtest/gtest.h>
#include <sstream>
#include <string>
+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<HttpResponse> 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<uint16_t>(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());
}
"<body><h1>Some header</h1></body>"
"</html>";
- 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);
"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());
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);
+}
+
}