Parcourir la source

[5088] Implemented date time conversions for HTTP.

Marcin Siodelski il y a 8 ans
Parent
commit
f95a200193

+ 2 - 2
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)

+ 137 - 0
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 <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

+ 64 - 0
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 <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

+ 9 - 6
src/lib/http/response.cc

@@ -5,9 +5,11 @@
 // 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 {
@@ -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

+ 3 - 1
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

+ 162 - 0
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 <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);
+}
+
+}

+ 9 - 3
src/lib/http/tests/response_json_unittests.cc

@@ -7,14 +7,18 @@
 #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:
 
@@ -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<uint16_t>(status_code)
@@ -50,7 +54,8 @@ public:
         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)) {
@@ -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());

+ 35 - 0
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<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

+ 19 - 14
src/lib/http/tests/response_unittests.cc

@@ -5,39 +5,34 @@
 // 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());
     }
@@ -51,7 +46,7 @@ TEST_F(HttpResponseTest, responseOK) {
         "<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);
@@ -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);
+}
+
 }