libkea_http_la_SOURCES += http_log.cc http_log.h
libkea_http_la_SOURCES += header_context.h
libkea_http_la_SOURCES += http_acceptor.h
+libkea_http_la_SOURCES += http_header.cc http_header.h
libkea_http_la_SOURCES += http_types.h
libkea_http_la_SOURCES += listener.cc listener.h
libkea_http_la_SOURCES += post_request.cc post_request.h
--- /dev/null
+// Copyright (C) 2017 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 <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <util/strutil.h>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace http {
+
+HttpHeader::HttpHeader(const std::string& header_name,
+ const std::string& header_value)
+ : header_name_(header_name), header_value_(header_value) {
+}
+
+uint64_t
+HttpHeader::getUint64Value() const {
+ try {
+ return (boost::lexical_cast<uint64_t>(header_value_));
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(BadValue, header_name_ << " HTTP header value "
+ << header_value_ << " is not a valid number");
+ }
+}
+
+std::string
+HttpHeader::getLowerCaseName() const {
+ std::string ln = header_name_;
+ util::str::lowercase(ln);
+ return (ln);
+}
+
+std::string
+HttpHeader::getLowerCaseValue() const {
+ std::string lc = header_value_;
+ util::str::lowercase(lc);
+ return (lc);
+}
+
+bool
+HttpHeader::isValueEqual(const std::string& v) const {
+ std::string lcv = v;
+ util::str::lowercase(lcv);
+ return (lcv == getLowerCaseValue());
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2017 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_HEADER_H
+#define HTTP_HEADER_H
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents HTTP header including a header name and value.
+///
+/// It includes methods for retrieving header name and value in lower case
+/// and for case insensitive comparison of header values.
+class HttpHeader {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param header_name Header name.
+ /// @param header_value Header value.
+ explicit HttpHeader(const std::string& header_name,
+ const std::string& header_value = "");
+
+ /// @brief Returns header name.
+ std::string getName() const {
+ return (header_name_);
+ }
+
+ /// @brief Returns header value.
+ std::string getValue() const {
+ return (header_value_);
+ }
+
+ /// @brief Returns header value as unsigned integer.
+ ///
+ /// @throw BadValue if the header value is not a valid number.
+ uint64_t getUint64Value() const;
+
+ /// @brief Returns lower case header name.
+ std::string getLowerCaseName() const;
+
+ /// @brief Returns lower case header value.
+ std::string getLowerCaseValue() const;
+
+ /// @brief Case insensitive comparison of header value.
+ ///
+ /// @param v Value to be compared.
+ ///
+ /// @return true if header value is equal, false otherwise.
+ bool isValueEqual(const std::string& v) const;
+
+private:
+
+ std::string header_name_; ///< Header name.
+ std::string header_value_; ///< Header value.
+};
+
+/// @brief Pointer to the @c HttpHeader class.
+typedef boost::shared_ptr<HttpHeader> HttpHeaderPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_HEADER_H
HttpRequest::requireHeader(const std::string& header_name) {
// Empty value denotes that the header is required but no specific
// value is expected.
- required_headers_[header_name] = "";
+ HttpHeaderPtr hdr(new HttpHeader(header_name));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
}
void
HttpRequest::requireHeaderValue(const std::string& header_name,
const std::string& header_value) {
- required_headers_[header_name] = header_value;
+ HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
}
bool
// If Content-Length is required the body must exist too. There may
// be probably some cases when Content-Length is not provided but
// the body is provided. But, probably not in our use cases.
- return (required_headers_.find("Content-Length") != required_headers_.end());
+ // Use lower case header name because this is how it is indexed in
+ // the storage.
+ return (required_headers_.find("content-length") != required_headers_.end());
}
void
for (auto header = context_->headers_.begin();
header != context_->headers_.end();
++header) {
- headers_[header->name_] = header->value_;
+ HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
+ headers_[hdr->getLowerCaseName()] = hdr;
}
// Iterate over required headers and check that they exist
if (header == headers_.end()) {
isc_throw(BadValue, "required header " << req_header->first
<< " not found in the HTTP request");
- } else if (!req_header->second.empty() &&
- header->second != req_header->second) {
+ } else if (!req_header->second->getValue().empty() &&
+ !header->second->isValueEqual(req_header->second->getValue())) {
// If specific value is required for the header, check
// that the value in the HTTP request matches it.
isc_throw(BadValue, "required header's " << header->first
- << " value is " << req_header->second
- << ", but " << header->second << " was found");
+ << " value is " << req_header->second->getValue()
+ << ", but " << header->second->getValue() << " was found");
}
}
context_->http_version_minor_));
}
-std::string
-HttpRequest::getHeaderValue(const std::string& header) const {
+HttpHeaderPtr
+HttpRequest::getHeader(const std::string& header_name) const {
checkCreated();
- auto header_it = headers_.find(header);
+ HttpHeader hdr(header_name);
+ auto header_it = headers_.find(hdr.getLowerCaseName());
if (header_it != headers_.end()) {
return (header_it->second);
}
+
// No such header.
- isc_throw(HttpRequestNonExistingHeader, header << " HTTP header"
+ isc_throw(HttpRequestNonExistingHeader, header_name << " HTTP header"
" not found in the request");
}
-uint64_t
-HttpRequest::getHeaderValueAsUint64(const std::string& header) const {
- // This will throw an exception if the header doesn't exist.
- std::string header_value = getHeaderValue(header);
+std::string
+HttpRequest::getHeaderValue(const std::string& header_name) const {
+ return (getHeader(header_name)->getValue());
+}
+uint64_t
+HttpRequest::getHeaderValueAsUint64(const std::string& header_name) const {
try {
- return (boost::lexical_cast<uint64_t>(header_value));
+ return (getHeader(header_name)->getUint64Value());
- } catch (const boost::bad_lexical_cast& ex) {
+ } catch (const std::exception& ex) {
// The specified header does exist, but the value is not a number.
- isc_throw(HttpRequestError, header << " HTTP header value "
- << header_value << " is not a valid number");
+ isc_throw(HttpRequestError, ex.what());
}
}
return (context_->body_);
}
+bool
+HttpRequest::isPersistent() const {
+ return (false);
+}
+
void
HttpRequest::checkCreated() const {
if (!created_) {
#define HTTP_REQUEST_H
#include <exceptions/exceptions.h>
+#include <http/http_header.h>
#include <http/http_types.h>
#include <http/request_context.h>
#include <boost/shared_ptr.hpp>
/// @brief Returns HTTP version number (major and minor).
HttpVersion getHttpVersion() const;
+ /// @brief Returns object encapsulating HTTP header.
+ ///
+ /// @param header_name HTTP header name.
+ HttpHeaderPtr getHeader(const std::string& header_name) const;
+
/// @brief Returns a value of the specified HTTP header.
///
- /// @param header Name of the HTTP header.
+ /// @param header_name Name of the HTTP header.
///
/// @throw HttpRequestError if the header doesn't exist.
- std::string getHeaderValue(const std::string& header) const;
+ std::string getHeaderValue(const std::string& header_name) const;
/// @brief Returns a value of the specified HTTP header as number.
///
- /// @param header Name of the HTTP header.
+ /// @param header_name Name of the HTTP header.
///
/// @throw HttpRequestError if the header doesn't exist or if the
/// header value is not number.
- uint64_t getHeaderValueAsUint64(const std::string& header) const;
+ uint64_t getHeaderValueAsUint64(const std::string& header_name) const;
/// @brief Returns HTTP message body as string.
std::string getBody() const;
return (finalized_);
}
+ /// @brief Checks if the client has requested persistent connection.
+ ///
+ /// For the HTTP/1.0 case, the connection is persistent if the client has
+ /// included Connection: keep-alive header. For the HTTP/1.1 case, the
+ /// connection is assumed to be persistent unless Connection: close header
+ /// has been included.
+ ///
+ /// @return true if the client has requested persistent connection, false
+ /// otherwise.
+ bool isPersistent() const;
+
//@}
protected:
/// If the set is empty, all versions are allowed.
std::set<HttpVersion> required_versions_;
+ /// @brief Map of HTTP headers indexed by lower case header names.
+ typedef std::map<std::string, HttpHeaderPtr> HttpHeaderMap;
+
/// @brief Map holding required HTTP headers.
///
- /// The key of this map specifies the HTTP header name. The value
- /// specifies the HTTP header value. If the value is empty, the
- /// header is required but the value of the header is not checked.
- /// If the value is non-empty, the value in the HTTP request must
- /// be equal to the value in the map.
- std::map<std::string, std::string> required_headers_;
+ /// The key of this map specifies the lower case HTTP header name.
+ /// If the value of the HTTP header is empty, the header is required
+ /// but the value of the header is not checked. If the value is
+ /// non-empty, the value in the HTTP request must be equal (case
+ /// insensitive) to the value in the map.
+ HttpHeaderMap required_headers_;
/// @brief Flag indicating whether @ref create was called.
bool created_;
Method method_;
/// @brief Parsed HTTP headers.
- std::map<std::string, std::string> headers_;
+ HttpHeaderMap headers_;
/// @brief Pointer to the @ref HttpRequestContext holding parsed
/// data.
libhttp_unittests_SOURCES = connection_pool_unittests.cc
libhttp_unittests_SOURCES += date_time_unittests.cc
+libhttp_unittests_SOURCES += http_header_unittests.cc
libhttp_unittests_SOURCES += listener_unittests.cc
libhttp_unittests_SOURCES += post_request_unittests.cc
libhttp_unittests_SOURCES += post_request_json_unittests.cc
--- /dev/null
+// Copyright (C) 2017 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 <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::http;
+
+namespace {
+
+// Test that HTTP header can be created.
+TEST(HttpHeader, create) {
+ HttpHeader hdr("Content-Type", "application/json");
+ EXPECT_EQ("Content-Type", hdr.getName());
+ EXPECT_EQ("application/json", hdr.getValue());
+}
+
+// Test that the numeric value can be retrieved from a header and that
+// an exception is thrown if the header value is not a valid number.
+TEST(HttpHeader, getUint64Value) {
+ HttpHeader hdr64("Content-Length", "64");
+ EXPECT_EQ(64, hdr64.getUint64Value());
+
+ HttpHeader hdr_foo("Content-Length", "foo");
+ EXPECT_THROW(hdr_foo.getUint64Value(), isc::BadValue);
+}
+
+// Test that header name can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseName) {
+ HttpHeader hdr("ConnectioN", "Keep-Alive");
+ EXPECT_EQ("connection", hdr.getLowerCaseName());
+}
+
+// Test that header value can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseValue) {
+ HttpHeader hdr("Connection", "Keep-Alive");
+ EXPECT_EQ("keep-alive", hdr.getLowerCaseValue());
+}
+
+// Test that header value comparison is case insensitive.
+TEST(HttpHeader, equalsCaseInsensitive) {
+ HttpHeader hdr("Connection", "KeEp-ALIve");
+ EXPECT_TRUE(hdr.isValueEqual("keep-alive"));
+ EXPECT_TRUE(hdr.isValueEqual("KEEP-ALIVE"));
+ EXPECT_TRUE(hdr.isValueEqual("kEeP-AlIvE"));
+}
+
+} // end of anonymous namespace
EXPECT_EQ(1, request_.getHttpVersion().minor_);
}
+// This test verifies that headers are case insensitive.
+TEST_F(HttpRequestParserTest, headersCaseInsensitive) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-type: text/html\r\n"
+ "connection: keep-Alive\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeader("Content-Type")->getValue());
+ EXPECT_EQ("keep-alive", request_.getHeader("Connection")->getLowerCaseValue());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
// This test verifies that other value of the HTTP version can be
// specified in the request.
TEST_F(HttpRequestParserTest, http20) {