diff --git a/inc/common.hpp b/inc/common.hpp index 3d4e0cf..241c2f7 100644 --- a/inc/common.hpp +++ b/inc/common.hpp @@ -18,21 +18,18 @@ #ifndef HTTP_COMMON_HPP #define HTTP_COMMON_HPP -#include -#include -#include +#include +#include #include +#include #include -#include +#include namespace http { - using URI = uri::URI; - using Limit = std::size_t; - - using buffer_t = std::shared_ptr; - - using Header_set = std::vector>; + using URI = uri::URI; + using Limit = std::size_t; + using Header_set = std::vector>; class Request; using Request_ptr = std::unique_ptr; diff --git a/inc/header.hpp b/inc/header.hpp index 0ab79b5..1eedddc 100644 --- a/inc/header.hpp +++ b/inc/header.hpp @@ -18,11 +18,9 @@ #ifndef HTTP_HEADER_HPP #define HTTP_HEADER_HPP +#include #include -#include -#include #include -#include #include #include "common.hpp" @@ -39,16 +37,12 @@ namespace http { * and provided method */ class Header { -private: - //----------------------------------------------- - // Internal class type aliases - using Const_iterator = Header_set::const_iterator; - //----------------------------------------------- public: /** * @brief Default constructor that limits the amount * of fields that can be added to 25 */ + template explicit Header() noexcept; /** @@ -58,28 +52,21 @@ class Header { * @param limit: * Capacity of how many fields that can be added */ + template explicit Header(const Limit limit) noexcept; /** * @brief Constructor that takes a stream of characters - * as a {std::string} object and parses it into a set - * of name-value pairs + * and parses it into a set of name-value pairs * - * @tparam T header_data: + * @param header_data: * The character stream of data to parse * * @param limit: * Capacity of how many fields can be added */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - explicit Header(T&& header_data, const Limit limit = 25); + template + explicit Header(const std::experimental::string_view header_data, const Limit limit = 25); /** * Default destructor @@ -104,172 +91,115 @@ class Header { /** * Default move assignemt operator */ - Header& operator = (Header&&) = default; - - /** - * @brief Set the limit of how many fields can be added - * - * @param limit: - * Capacity of how many fields can be added - */ - void set_limit(const Limit limit) noexcept; + Header& operator = (Header&&) noexcept = default; /** * @brief Get the current capacity * * @return The current capacity of the set */ - Limit get_limit() const noexcept; + template + Limit limit() const noexcept; /** * @brief Add a new field to the current set * - * @tparam F field: + * @param field: * The name of the field * - * @tparam V value: + * @param value: * The field value * * @return true if the field was added, false otherwise */ - template - < - typename F, typename V, - typename = std::enable_if_t - >>::value and - std::is_same - >>::value> - > - bool add_field(F&& field, V&& value); + template + bool add_field(const std::experimental::string_view field, const std::experimental::string_view value); /** * @brief Add a set of fields to the current set from - * a {std::string} object in the following format: + * a stream of characters in the following format: * * "name: value\r\n" * "name: value\r\n" * ... * - * @tparam D data - The set of fields to add + * @param data + * The set of fields to add */ - template - < - typename D, - typename = std::enable_if_t - >>::value> - > - void add_fields(D&& data); + void add_fields(const std::experimental::string_view data); /** * @brief Change the value of the specified field * - * If the field is absent from the set it will - * be added with the associated value once its - * within capacity + * If the field is absent it will be added with + * the associated value once its within capacity * - * @tparam F field: + * @param field: * The name of the field - * @tparam V value: + * @param value: * The field value * * @return true if successful, false otherwise */ - template - < - typename F, typename V, - typename = std::enable_if_t - >>::value and - std::is_same - >>::value> - > - bool set_field(F&& field, V&& value); + template + bool set_field(const std::experimental::string_view field, const std::experimental::string_view value); /** * @brief Get the value associated with a field * - * Should call before calling this - * - * @tparam F field: + * @param field: * The name of the field * * @return The value associated with the specified - * field name + * field name if exist, empty view otherwise */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - const std::string& get_value(F&& field) const noexcept; + template + std::experimental::string_view value(const std::experimental::string_view field) const noexcept; /** - * @brief Check to see if the specified field is a - * member of the set of fields + * @brief Check to see if the specified field is present * - * @tparam F field: + * @param field: * The name of the field * * @return true if the field is a member, false otherwise */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - bool has_field(F&& field) const noexcept; + template + bool has_field(const std::experimental::string_view field) const noexcept; /** - * @brief Check to see if the set of fields is empty + * @brief Check to see if there are no fields present * - * @return true if there are no fields within the set, + * @return true if there are no fields present, * false otherwise */ + template bool is_empty() const noexcept; /** * @brief Check to see how many fields are currently - * in the set of fields + * present * - * @return The amount of fields currently in the set + * @return The amount of fields currently present */ + template Limit size() const noexcept; /** - * @brief Remove all fields from the set of fields with the + * @brief Remove all fields currently present with the * specified name * - * @tparam F field: + * @param field: * The name of the field to remove */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - void erase(F&& field) noexcept; + template + void erase(const std::experimental::string_view field) noexcept; /** - * @brief Remove all fields from the set of fields leaving - * it empty + * @brief Remove all fields leaving none present */ + template void clear() noexcept; /** @@ -278,7 +208,7 @@ class Header { * * @return A string representation */ - virtual std::string to_string() const; + std::string to_string() const; /** * @brief Operator to transform this class @@ -292,252 +222,208 @@ class Header { //----------------------------------------------- /** - * @brief Find the location of a field within the set of - * fields - * - * @tparam F field: - * The field name to locate a field within the set of fields + * @brief Set the limit of how many fields can be added * - * @return Iterator to the location of the field, else - * location to the end of the sequence + * @param limit: + * Capacity of how many fields can be added */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - Const_iterator find(F&& field) const noexcept; + template + void set_limit(const Limit limit) noexcept; /** - * @brief Operator to stream the contents of the set of fields - * into the specified output device - * - * The output format is as follows: - * field : value "\r\n" - * field : value "\r\n" - * ... - * "\r\n" - * - * @param output_device: - * The output device to stream the contents from an instance of this - * class into + * @brief Find the location of a field within the set of + * fields * - * @param header: - * An instance of this class + * @param field: + * The field name to locate a field if present * - * @return Reference to the specified output device + * @return Iterator to the location of the field, else + * location to the end of the sequence */ - friend std::ostream& operator << (std::ostream& output_device, const Header& header); -}; //< class Header + template + Header_set::const_iterator find(const std::experimental::string_view field) const noexcept; +}; /**--v----------- Implementation Details -----------v--**/ +template inline Header::Header() noexcept { - set_limit(25); + set_limit(25U); } +template inline Header::Header(const Limit limit) noexcept { set_limit(limit); } -template -inline Header::Header(T&& header_data, const Limit limit) +template +inline Header::Header(const std::experimental::string_view header_data, const Limit limit) : Header {limit} { add_fields(header_data); } -inline void Header::set_limit(const Limit limit) noexcept { - fields_.reserve(limit); -} - -inline Limit Header::get_limit() const noexcept { +template +inline Limit Header::limit() const noexcept { return fields_.capacity(); } -template -inline bool Header::add_field(Field&& field, Value&& value) { - if (field.empty()) return false; - //----------------------------------- - if (size() < fields_.capacity()) { - fields_.emplace_back(std::forward(field), std::forward(value)); +template +inline bool Header::add_field(const std::experimental::string_view field, const std::experimental::string_view value) { + if ((not field.empty()) and (size() < limit())) { + fields_.emplace_back(field, value); return true; } - //----------------------------------- + return false; } -template -inline void Header::add_fields(Data&& data) { - if (data.empty()) return; - //----------------------------------- - auto iterator = data.cbegin(); - auto sentinel = data.cend(); +inline void Header::add_fields(std::experimental::string_view fields_view) { + if (fields_view.empty() or (size() == limit())) return; //----------------------------------- - std::string field; - std::string value; - field.reserve(24); - value.reserve(64); + std::experimental::string_view field {}; + std::experimental::string_view value {}; + std::experimental::string_view::size_type base {0U}; + std::experimental::string_view::size_type break_point {}; //----------------------------------- - Limit limit {0}; - int character = *iterator; - const int stop_char = std::char_traits::eof(); + fields_view.remove_prefix(fields_view.find_first_not_of(' ')); //----------------------------------- - while (iterator not_eq sentinel - and character not_eq stop_char - and limit < fields_.capacity()) - { - field.clear(); - value.clear(); - //----------------------------------- - while (iterator not_eq sentinel and isspace(character)) { - character = *++iterator; - } - //----------------------------------- - while (iterator not_eq sentinel - and character not_eq stop_char - and character not_eq ':' - and not iscntrl(character) - and not isspace(character)) - { - field += character; - character = *++iterator; - } - //----------------------------------- - while (iterator not_eq sentinel and isspace(character)) { - character = *++iterator; + while (size() < limit()) { + if ((break_point = fields_view.find(':')) not_eq std::experimental::string_view::npos) { + field = fields_view.substr(base, break_point); + //----------------------------------- + fields_view.remove_prefix(field.length() + 1U); + fields_view.remove_prefix(fields_view.find_first_not_of(' ')); } - //----------------------------------- - if (character not_eq ':') return; - //----------------------------------- - if (iterator not_eq sentinel) character = *++iterator; - //----------------------------------- - while (iterator not_eq sentinel and isspace(character)) { - character = *++iterator; + else { + break; } - //----------------------------------- -parse_value: - while (iterator not_eq sentinel - and character not_eq stop_char - and not iscntrl(character) - and character not_eq '\r' - and character not_eq '\n') - { - value += character; - character = *++iterator; + + if ((break_point = fields_view.find("\r\n")) not_eq std::experimental::string_view::npos) { + value = fields_view.substr(base, break_point); + fields_.emplace_back(field, value); + fields_view.remove_prefix(value.length() + 2U); } - //----------------------------------- - int lws_count {0}; - //----------------------------------- - while (iterator not_eq sentinel - and (character == '\r' || character == '\n')) - { - character = *++iterator; - ++lws_count; + else if ((break_point = fields_view.find('\n')) not_eq std::experimental::string_view::npos) { + value = fields_view.substr(base, break_point); + fields_.emplace_back(field, value); + fields_view.remove_prefix(value.length() + 1U); } - //----------------------------------- - if (lws_count == 3) break; - //----------------------------------- - while (iterator not_eq sentinel and isspace(character)) { - character = *++iterator; - //----------------------------------- - if (iterator not_eq sentinel - and ((iterator + 1) not_eq sentinel) - and isspace(*(iterator + 1))) - continue; - //----------------------------------- - goto parse_value; + + if (fields_view[0] == '\r' or fields_view[0] == '\n') { + break; } - //----------------------------------- - add_field(field, value); - //----------------------------------- - ++limit; - //----------------------------------- } } -template -inline bool Header::set_field(Field&& field, Value&& value) { +template +inline bool Header::set_field(const std::experimental::string_view field, const std::experimental::string_view value) { if (field.empty() || value.empty()) return false; //----------------------------------- - auto target = find(field); + const auto target = find(field); //----------------------------------- - if (target not_eq fields_.end()) { - const_cast((*target).second) = std::forward(value); + if (target not_eq fields_.cend()) { + const_cast(target->second) = value; return true; } - else return add_field(std::forward(field), std::forward(value)); + else return add_field(field, value); } -template -inline const std::string& Header::get_value(Field&& field) const noexcept { +template +inline std::experimental::string_view Header::value(const std::experimental::string_view field) const noexcept { if (field.empty()) return field; - //----------------------------------- - return find(std::forward(field))->second; + const auto it = find(field); + return (it not_eq fields_.cend()) ? it->second : std::experimental::string_view{}; } -template -inline bool Header::has_field(Field&& field) const noexcept { +template +inline bool Header::has_field(const std::experimental::string_view field) const noexcept { if (field.empty()) return false; //----------------------------------- - return find(std::forward(field)) not_eq fields_.end(); + return find(field) not_eq fields_.cend(); } +template inline bool Header::is_empty() const noexcept { return fields_.empty(); } +template inline Limit Header::size() const noexcept { return fields_.size(); } -template -inline void Header::erase(Field&& field) noexcept { +template +inline void Header::erase(const std::experimental::string_view field) noexcept { if (field.empty()) return; //----------------------------------- - auto target = find(std::forward(field)); - //----------------------------------- - if (target not_eq fields_.end()) fields_.erase(target); + Header_set::const_iterator target; + while ((target = find(field)) not_eq fields_.cend()) { + fields_.erase(target); + } } +template inline void Header::clear() noexcept { fields_.clear(); } -inline static std::string string_to_lower_case(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - return s; -} - -template -inline Header::Const_iterator Header::find(Field&& field) const noexcept { - if (field.empty()) return fields_.end(); - //----------------------------------- - return - std::find_if(fields_.begin(), fields_.end(), [&field](const auto& f) { - return string_to_lower_case(f.first) == string_to_lower_case(field); - }); -} - inline std::string Header::to_string() const { - std::ostringstream header; - //----------------------------------- - for (const auto& field : fields_) { - header << field.first << ": " << field.second << "\r\n"; + if (size()) { + std::ostringstream header; + //----------------------------------- + for (const auto& field : fields_) { + header << field.first << ": " << field.second << "\r\n"; + } + header << "\r\n"; + //----------------------------------- + return header.str(); } - //----------------------------------- - header << "\r\n"; - //----------------------------------- - return header.str(); + + return {}; } inline Header::operator std::string () const { return to_string(); } +template +inline void Header::set_limit(const Limit limit) noexcept { + fields_.reserve(limit); +} + +template +inline Header_set::const_iterator Header::find(const std::experimental::string_view field) const noexcept { + if (field.empty()) return fields_.cend(); + //----------------------------------- + return + std::find_if(fields_.cbegin(), fields_.cend(), [&field](const auto __) { + return (__.first.length() == field.length()) + and std::equal(__.first.data(), __.first.data() + __.first.length(), field.data(), [](const auto a, const auto b) { + return std::tolower(a) == std::tolower(b); + }); + }); +} + +/** + * @brief Operator to stream the of this class into the + * specified output device + * + * The output format is as follows: + * field : value "\r\n" + * field : value "\r\n" + * ... + * "\r\n" + * + * @param output_device: + * The output device to stream the contents from an instance of this + * class into + * + * @param header: + * An instance of this class + * + * @return Reference to the specified output device + */ inline std::ostream& operator << (std::ostream& output_device, const Header& header) { return output_device << header.to_string(); } diff --git a/inc/header_fields.hpp b/inc/header_fields.hpp index a71cb36..dd827fd 100644 --- a/inc/header_fields.hpp +++ b/inc/header_fields.hpp @@ -18,71 +18,58 @@ #ifndef HTTP_HEADER_FIELDS_HPP #define HTTP_HEADER_FIELDS_HPP -#include +#include namespace http { namespace header_fields { //------------------------------------------------ -using Field = const std::string; -//------------------------------------------------ -//------------------------------------------------ -namespace Request { -Field Accept {"Accept"}; -Field Accept_Charset {"Accept-Charset"}; -Field Accept_Encoding {"Accept-Encoding"}; -Field Accept_Language {"Accept-Language"}; -Field Authorization {"Authorization"}; -Field Connection {"Connection"}; -Field Cookie {"Cookie"}; -Field Expect {"Expect"}; -Field From {"From"}; -Field Host {"Host"}; -Field HTTP2_Settings {"HTTP2-Settings"}; -Field If_Match {"If-Match"}; -Field If_Modified_Since {"If-Modified-Since"}; -Field If_None_Match {"If-None-Match"}; -Field If_Range {"If-Range"}; -Field If_Unmodified_Since {"If-Unmodified-Since"}; -Field Max_Forwards {"Max-Forwards"}; -Field Proxy_Authorization {"Proxy-Authorization"}; -Field Range {"Range"}; -Field Referer {"Referer"}; -Field TE {"TE"}; -Field Upgrade {"Upgrade"}; -Field User_Agent {"User-Agent"}; -} //< namespace Request -//------------------------------------------------ -//------------------------------------------------ -namespace Response { -Field Accept_Ranges {"Accept-Ranges"}; -Field Age {"Age"}; -Field Connection {"Connection"}; -Field ETag {"ETag"}; -Field Location {"Location"}; -Field Proxy_Authenticate {"Proxy-Authenticate"}; -Field Retry_After {"Retry-After"}; -Field Server {"Server"}; -Field Set_Cookie {"Set-Cookie"}; -Field Upgrade {"Upgrade"}; -Field Vary {"Vary"}; -Field WWW_Authenticate {"WWW-Authenticate"}; -} //< namespace Response -//------------------------------------------------ -//------------------------------------------------ -namespace Entity { -Field Allow {"Allow"}; -Field Content_Encoding {"Content-Encoding"}; -Field Content_Language {"Content-Language"}; -Field Content_Length {"Content-Length"}; -Field Content_Location {"Content-Location"}; -Field Content_MD5 {"Content-MD5"}; -Field Content_Range {"Content-Range"}; -Field Content_Type {"Content-Type"}; -Field Expires {"Expires"}; -Field Last_Modified {"Last-Modified"}; -} //< namespace Entity +using Field = const std::experimental::string_view; //------------------------------------------------ //------------------------------------------------ +Field accept {"Accept"}; +Field accept_charset {"Accept-Charset"}; +Field accept_encoding {"Accept-Encoding"}; +Field accept_ranges {"Accept-Ranges"}; +Field accept_language {"Accept-Language"}; +Field allow {"Allow"}; +Field age {"Age"}; +Field authorization {"Authorization"}; +Field connection {"Connection"}; +Field cookie {"Cookie"}; +Field content_encoding {"Content-Encoding"}; +Field content_language {"Content-Language"}; +Field content_length {"Content-Length"}; +Field content_location {"Content-Location"}; +Field content_md5 {"Content-MD5"}; +Field content_range {"Content-Range"}; +Field content_type {"Content-Type"}; +Field date {"Date"}; +Field etag {"ETag"}; +Field expect {"Expect"}; +Field expires {"Expires"}; +Field from {"From"}; +Field host {"Host"}; +Field http2_settings {"HTTP2-Settings"}; +Field if_match {"If-Match"}; +Field if_modified_since {"If-Modified-Since"}; +Field if_none_match {"If-None-Match"}; +Field if_range {"If-Range"}; +Field if_unmodified_since {"If-Unmodified-Since"}; +Field last_modified {"Last-Modified"}; +Field location {"Location"}; +Field max_forwards {"Max-Forwards"}; +Field proxy_authorization {"Proxy-Authorization"}; +Field proxy_authenticate {"Proxy-Authenticate"}; +Field range {"Range"}; +Field referer {"Referer"}; +Field retry_after {"Retry-After"}; +Field server {"Server"}; +Field set_cookie {"Set-Cookie"}; +Field te {"TE"}; +Field upgrade {"Upgrade"}; +Field user_agent {"User-Agent"}; +Field vary {"Vary"}; +Field www_authenticate {"WWW-Authenticate"}; } //< namespace header_fields } //< namespace http diff --git a/inc/http b/inc/http index 5844a33..9f70120 100644 --- a/inc/http +++ b/inc/http @@ -1,3 +1,4 @@ +// -*- C++ -*- // This file is a part of the IncludeOS unikernel - www.includeos.org // // Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences @@ -15,8 +16,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// -*-C++-*- - #ifndef ___HTTP_API___ #define ___HTTP_API___ diff --git a/inc/message.hpp b/inc/message.hpp index 1bf4bfc..4a69dd6 100644 --- a/inc/message.hpp +++ b/inc/message.hpp @@ -18,8 +18,6 @@ #ifndef HTTP_MESSAGE_HPP #define HTTP_MESSAGE_HPP -#include - #include "time.hpp" #include "header.hpp" @@ -30,18 +28,12 @@ namespace http { * HTTP message */ class Message { -private: - //---------------------------------------- - // Internal class type aliases - using HSize = Limit; - using HValue = const std::string&; - using Message_Body = std::string; - //---------------------------------------- public: /** * @brief Default constructor */ - explicit Message() = default; + template + explicit Message(); /** * @brief Constructor to specify the limit of how many @@ -49,8 +41,9 @@ class Message { * * @param limit: * Maximum number of fields that can be added to the - * message + * message */ + template explicit Message(const Limit limit) noexcept; /** @@ -76,235 +69,47 @@ class Message { /** * @brief Default move assignment operator */ - Message& operator = (Message&&) = default; - - /** - * @brief Set the maximum number of fields that - * can be added to the header section for an instance - * of this class - * - * @param limit: - * The maximum number of fields that can be added to - * the header section - * - * @return The object that invoked this method - */ - Message& set_header_limit(const Limit limit) noexcept; - - /** - * @brief Get the current field limit for this - * message - * - * @return The current field limit - */ - Limit get_header_limit() const noexcept; + Message& operator = (Message&&) noexcept = default; /** - * @brief Add a new field to the current set of - * headers - * - * @tparam F field: - * The name of the field - * - * @tparam V value: - * The field value + * @brief Get a modifiable reference to the object that + * represents the header section * - * @return The object that invoked this method + * @return Modifiable reference to the header object */ - template - < - typename F, typename V, - typename = std::enable_if_t - >>::value and - std::is_same - >>::value> - > - Message& add_header(F&& field, V&& value); - - /** - * @brief Add a set of fields to the header section of - * the message from a {std::string} object in the - * following format: - * - * "name: value\r\n" - * "name: value\r\n" - * ... - * - * @tparam D data: - * The set of fields to add to the header section of this - * message - * - * @return The object that invoked this method - */ - template - < - typename D, - typename = std::enable_if_t - >>::value> - > - Message& add_headers(D&& data); - - /** - * @brief Change the value of the specified field - * - * If the field is absent from the message it - * will be added with the associated value if - * within capacity of maximum allowable limit - * - * @tparam F field: - * The name of the field - - * @tparam V value: - * The field value - * - * @return The object that invoked this method - */ - template - < - typename F, typename V, - typename = std::enable_if_t - >>::value and - std::is_same - >>::value> - > - Message& set_header(F&& field, V&& value); + template + Header& header() noexcept; /** * @brief Get a read-only reference to the object that - * represents the header section in this message - * - * @return The header in this message - */ - const Header& get_header() const noexcept; - - /** - * @brief Get the value associated with the - * specified field name - * - * Should call before calling this - * - * @tparam F field: - * The field name to get associated value + * represents the header section * - * @return The value associated with the specified - * field name - */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - HValue header_value(F&& field) const noexcept; - - /** - * @brief Check if the specified field is within - * this message - * - * @tparam F field: - * The field name to search for - * - * @return true is present, false otherwise - */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - bool has_header(F&& field) const noexcept; - - /** - * @brief Remove the specified field from this - * message - * - * @tparam F field: - * The header field to remove from this message - * - * @return The object that invoked this method + * @return Read-only reference to the header object */ - template - < - typename F, - typename = std::enable_if_t - >>::value> - > - Message& erase_header(F&& field) noexcept; - - /** - * @brief Remove all header fields from this - * message - * - * @return The object that invoked this method - */ - Message& clear_headers() noexcept; - - /** - * @brief Check if there are no fields in this - * message - * - * @return true if the message has no fields, - * false otherwise - */ - bool is_header_empty() const noexcept; - - /** - * @brief Get the number of fields in this - * message - * - * @return The number of fields in this message - */ - HSize header_size() const noexcept; + template + const Header& header() const noexcept; /** * @brief Add an entity to the message * - * @tparam E message_body: - * The entity to be sent with the message + * @param view: + * A view of the entity to be sent with the message * * @return The object that invoked this method */ - template - < - typename E, - typename = std::enable_if_t - >>::value> - > - Message& add_body(E&& message_body); + template + Message& add_body(const std::experimental::string_view view); /** * @brief Append data to the entity of the message * - * @tparam D data: - * The data to append to the entity of the message + * @param view: + * A view of the data to append to the entity of the message * * @return The object that invoked this method */ - template - < - typename D, - typename = std::enable_if_t - >>::value> - > - Message& append_body(D&& data); + template + Message& append_body(const std::experimental::string_view view); /** * @brief Get a read-only reference to the entity in @@ -313,13 +118,15 @@ class Message { * @return A read-only reference to the entity in * this the message */ - const Message_Body& get_body() const noexcept; + template + std::experimental::string_view body() const noexcept; /** * @brief Remove the entity from the message * * @return The object that invoked this method */ + template Message& clear_body() noexcept; /** @@ -346,109 +153,72 @@ class Message { private: //------------------------------ // Class data members - Header header_fields_; - Message_Body message_body_; + Header header_fields_; + std::string content_length_; + std::experimental::string_view message_body_; + + std::string extended_body_; //------------------------------ }; //< class Message /**--v----------- Implementation Details -----------v--**/ +template +inline Message::Message() {} + +template inline Message::Message(const Limit limit) noexcept: - header_fields_{limit}, - message_body_{} + header_fields_{limit} {} -inline Message& Message::set_header_limit(const Limit limit) noexcept { - header_fields_.set_limit(limit); - return *this; -} - -inline Limit Message::get_header_limit() const noexcept { - return header_fields_.get_limit(); -} - -template -inline Message& Message::add_header(Field&& field, Value&& value) { - header_fields_.add_field(std::forward(field), std::forward(value)); - return *this; -} - -template -inline Message& Message::add_headers(Data&& data) { - header_fields_.add_fields(std::forward(data)); - return *this; -} - -template -inline Message& Message::set_header(Field&& field, Value&& value) { - header_fields_.set_field(std::forward(field), std::forward(value)); - return *this; -} - -inline const Header& Message::get_header() const noexcept { +template +inline Header& Message::header() noexcept { return header_fields_; } -template -inline Message::HValue Message::header_value(Field&& field) const noexcept { - return header_fields_.get_value(std::forward(field)); -} - -template -inline bool Message::has_header(Field&& field) const noexcept { - return header_fields_.has_field(std::forward(field)); +template +inline const Header& Message::header() const noexcept { + return header_fields_; } -template -inline Message& Message::erase_header(Field&& field) noexcept { - header_fields_.erase(std::forward(field)); +template +inline Message& Message::add_body(const std::experimental::string_view view) { + if (view.empty()) return *this; + message_body_ = view; + content_length_ = std::to_string(message_body_.length()); + header_fields_.add_field(header_fields::content_length, content_length_); return *this; } -inline Message& Message::clear_headers() noexcept { - header_fields_.clear(); - return *this; -} - -inline bool Message::is_header_empty() const noexcept { - return header_fields_.is_empty(); -} - -inline Message::HSize Message::header_size() const noexcept { - return header_fields_.size(); -} - -template -inline Message& Message::add_body(Entity&& message_body) { - if (message_body.empty()) return *this; - //----------------------------------- - message_body_ = std::forward(message_body); +template +inline Message& Message::append_body(const std::experimental::string_view view) { + if (view.empty()) return *this; //----------------------------------- - return add_header(header_fields::Entity::Content_Length, - std::to_string(message_body_.size())); -} - -template -inline Message& Message::append_body(Data&& data) { - if (data.empty()) return *this; - //----------------------------------- - message_body_.insert(message_body_.cend(), data.cbegin(), data.cend()); + extended_body_ = message_body_.to_string(); + extended_body_.insert(extended_body_.cend(), view.data(), view.data() + view.length()); + message_body_ = extended_body_; + content_length_ = std::to_string(message_body_.length()); //----------------------------------- - return set_header(header_fields::Entity::Content_Length, - std::to_string(message_body_.size())); + header_fields_.set_field(header_fields::content_length, content_length_); + return *this; } -inline const Message::Message_Body& Message::get_body() const noexcept { +template +inline std::experimental::string_view Message::body() const noexcept { return message_body_; } +template inline Message& Message::clear_body() noexcept { - message_body_.clear(); - return erase_header(header_fields::Entity::Content_Length); + message_body_ = {}; + extended_body_.clear(); + header_fields_.erase(header_fields::content_length); + return *this; } inline Message& Message::reset() noexcept { - return clear_headers().clear_body(); + header_fields_.clear(); + return clear_body(); } inline std::string Message::to_string() const { diff --git a/inc/methods.hpp b/inc/methods.hpp index 620a862..5dd3956 100644 --- a/inc/methods.hpp +++ b/inc/methods.hpp @@ -19,7 +19,7 @@ #define HTTP_METHODS_HPP #include -#include +#include #include #include @@ -45,9 +45,10 @@ namespace http { * * @return The string representation of the code */ - static const std::string& str(const Method m) { + template + std::experimental::string_view str(const Method m) noexcept { - const static std::array strings + const static std::array views { { "GET", "POST", "PUT", "DELETE", "OPTIONS", @@ -55,13 +56,13 @@ namespace http { } }; - auto e = strings.size() - 1; + const auto e = (views.size() - 1); if ((m >= 0) and (m < e)) { - return strings[m]; + return views[m]; } - return strings[e]; + return views[e]; } /** @@ -73,9 +74,10 @@ namespace http { * * @return The code mapped to the method string **/ - inline Method code(const std::string& method) noexcept { + template + Method code(const std::experimental::string_view method) noexcept { - const static std::unordered_map code_map { + const static std::unordered_map code_map { {"GET", GET}, {"POST", POST}, {"PUT", PUT}, @@ -87,17 +89,19 @@ namespace http { {"PATCH", PATCH} }; - auto it = code_map.find(method); + const auto it = code_map.find(method); - return (it not_eq code_map.end()) ? it->second : INVALID; + return (it not_eq code_map.cend()) ? it->second : INVALID; } + template inline bool is_content_length_allowed(const Method method) noexcept { - return (method == POST) || (method == PUT); + return (method == POST) or (method == PUT); } + template inline bool is_content_length_required(const Method method) noexcept { - return (method == POST) || (method == PUT); + return (method == POST) or (method == PUT); } } //< namespace method diff --git a/inc/mime_types.hpp b/inc/mime_types.hpp index ae275ec..6670468 100644 --- a/inc/mime_types.hpp +++ b/inc/mime_types.hpp @@ -18,13 +18,13 @@ #ifndef HTTP_MIME_TYPES_HPP #define HTTP_MIME_TYPES_HPP -#include +#include #include namespace http { //------------------------------------------------ -using Extension = std::string; -using Mime_type = std::string; +using Extension = std::experimental::string_view; +using Mime_type = std::experimental::string_view; using Mime_type_table = std::unordered_map; //------------------------------------------------ const Mime_type_table mime_types { @@ -96,12 +96,12 @@ const Mime_type_table mime_types { {"msm" , "application/octet-stream"} }; //< mime_types -inline const Mime_type& extension_to_type(const Extension& extension) noexcept { - auto it = mime_types.find(extension); +inline std::experimental::string_view ext_to_mime_type(const std::experimental::string_view extension) noexcept { + const auto it = mime_types.find(extension); //------------------------------------------------ - return (it not_eq mime_types.end()) + return (it not_eq mime_types.cend()) ? it->second - : const_cast(mime_types)["bin"]; + : "application/octet-stream"; } } //< namespace http diff --git a/inc/request.hpp b/inc/request.hpp index 13785dd..66b8650 100644 --- a/inc/request.hpp +++ b/inc/request.hpp @@ -31,34 +31,25 @@ class Request : public Message { public: /** * @brief Default constructor - * - * Constructs a request message as follows: - * - * */ - explicit Request() = default; + template + explicit Request(); /** * @brief Construct a request message from the - * incoming character stream of data which is - * a {std::string} object + * incoming character stream of data * - * @tparam T request: + * @param request: * The character stream of data * + * @param len: + * The length of the character stream + * * @param limit: * Capacity of how many fields can be added to * the header section */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - explicit Request(T&& request, const Limit limit = 25); + explicit Request(std::string request, const Limit limit = 25); /** * @brief Default copy constructor @@ -90,6 +81,7 @@ class Request : public Message { * * @return The method of the request */ + template Method method() const noexcept; /** @@ -100,7 +92,8 @@ class Request : public Message { * * @return The object that invoked this method */ - Request& set_method(const Method method); + template + Request& set_method(const Method method) noexcept; /** * @brief Get read-only reference to the URI of @@ -109,6 +102,7 @@ class Request : public Message { * @return Read-only reference to the URI of * the request message */ + template const URI& uri() const noexcept; /** @@ -119,6 +113,7 @@ class Request : public Message { * * @return The object that invoked this method */ + template Request& set_uri(const URI& uri); /** @@ -126,6 +121,7 @@ class Request : public Message { * * @return The version of the request */ + template Version version() const noexcept; /** @@ -136,47 +132,34 @@ class Request : public Message { * * @return The object that invoked this method */ + template Request& set_version(const Version version) noexcept; /** * @brief Get the value associated with the name - * field from a query string + * from a query string * - * @tparam T name: + * @param name: * The name to find the associated value * * @return The associated value if name was found, * an empty string otherwise */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - const std::string& query_value(T&& name) noexcept; + template + std::experimental::string_view query_value(const std::experimental::string_view name) noexcept; /** * @brief Get the value associated with the name - * field from the message body in a post request + * from the message body in a post request * - * @tparam T name: + * @param name: * The name to find the associated value * * @return The associated value if name was found, * an empty string otherwise */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - std::string post_value(T&& name) const noexcept; + template + std::experimental::string_view post_value(const std::experimental::string_view name) const noexcept; /** * @brief Reset the request message as if it was now @@ -202,75 +185,86 @@ class Request : public Message { private: //---------------------------------------- // Class data members + std::string request_; Request_line request_line_; //---------------------------------------- }; //< class Request /**--v----------- Implementation Details -----------v--**/ -template -inline Request::Request(Ingress&& request, const Limit limit) +template +inline Request::Request() {} + +inline Request::Request(std::string request, const Limit limit) : Message{limit} - , request_line_{request} + , request_{std::move(request)} { - add_headers(request); - std::size_t start_of_body; - if ((start_of_body = request.find("\r\n\r\n")) not_eq std::string::npos) { - add_body(request.substr(start_of_body + 4)); - } else if ((start_of_body = request.find("\n\n")) not_eq std::string::npos) { - add_body(request.substr(start_of_body + 2)); + std::experimental::string_view request_view {request_}; + request_line_ = Request_line{request_view}; + header().add_fields(request_view); + std::experimental::string_view::size_type start_of_body; + if ((start_of_body = request_view.find("\r\n\r\n")) not_eq std::experimental::string_view::npos) { + add_body(request_view.substr(start_of_body + 4U)); + } else if ((start_of_body = request_view.find("\n\n")) not_eq std::experimental::string_view::npos) { + add_body(request_view.substr(start_of_body + 2U)); } } +template inline Method Request::method() const noexcept { - return request_line_.get_method(); + return request_line_.method(); } -inline Request& Request::set_method(const Method method) { +template +inline Request& Request::set_method(const Method method) noexcept { request_line_.set_method(method); return *this; } +template inline const URI& Request::uri() const noexcept { - return request_line_.get_uri(); + return request_line_.uri(); } +template inline Request& Request::set_uri(const URI& uri) { request_line_.set_uri(uri); return *this; } +template inline Version Request::version() const noexcept { - return request_line_.get_version(); + return request_line_.version(); } +template inline Request& Request::set_version(const Version version) noexcept { request_line_.set_version(version); return *this; } -template -inline const std::string& Request::query_value(Name&& name) noexcept { - return request_line_.get_uri().query(std::forward(name)); +template +inline std::experimental::string_view Request::query_value(const std::experimental::string_view name) noexcept { + return request_line_.uri().query(name.to_string()); } -template -inline std::string Request::post_value(Name&& name) const noexcept { - if ((method() not_eq POST) || get_body().empty() || name.empty()) { - return std::string{}; +template +inline std::experimental::string_view Request::post_value(const std::experimental::string_view name) const noexcept { + if ((method() not_eq POST) or name.empty() or body().empty()) { + return {}; } //--------------------------------- - auto target = get_body().find(std::forward(name)); + const auto target = body().find(name); //--------------------------------- - if (target == std::string::npos) return std::string{}; + if (target == std::experimental::string_view::npos) return {}; //--------------------------------- - auto focal_point = get_body().substr(target); + auto focal_point = body().substr(target); //--------------------------------- focal_point = focal_point.substr(0, focal_point.find_first_of('&')); //--------------------------------- - auto lock_and_load = focal_point.find('='); + const auto lock_and_load = focal_point.find('='); //--------------------------------- - if (lock_and_load == std::string::npos) return std::string{}; + if (lock_and_load == std::experimental::string_view::npos) return {}; //--------------------------------- return focal_point.substr(lock_and_load + 1); } @@ -295,8 +289,17 @@ inline Request::operator std::string () const { return to_string(); } -inline Request_ptr make_request(buffer_t buf, const size_t len) { - return std::make_unique(std::string{reinterpret_cast(buf.get()), len}); +/** + * @brief Get a std::unique_ptr to a Request object + * + * @param request + * A character stream of data representing an HTTP request message + * + * @return A std::unique_ptr to a Request object + */ +template +inline Request_ptr make_request(std::string request) { + return std::make_unique(std::move(request)); } inline std::ostream& operator << (std::ostream& output_device, const Request& req) { diff --git a/inc/request_line.hpp b/inc/request_line.hpp index d75e02a..49704ec 100644 --- a/inc/request_line.hpp +++ b/inc/request_line.hpp @@ -36,25 +36,17 @@ class Request_line { /** * @brief Default constructor */ - explicit Request_line() = default; + template + explicit Request_line(); /** * @brief Construct a {Request-Line} from the incoming - * character stream of data which is a {std::string} - * object + * character stream of data * - * @tparam T request: + * @param request: * The character stream of data */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - explicit Request_line(T&& request); + explicit Request_line(std::experimental::string_view request); /** * @brief Default copy constructor @@ -86,7 +78,8 @@ class Request_line { * * @return The method of the message */ - Method get_method() const noexcept; + template + Method method() const noexcept; /** * @brief Set the method of the message @@ -94,6 +87,7 @@ class Request_line { * @param method: * The method of the message */ + template void set_method(const Method method) noexcept; /** @@ -102,7 +96,8 @@ class Request_line { * * @return A reference to the URI object */ - URI& get_uri() noexcept; + template + URI& uri() noexcept; /** * @brief Get a read-only reference to the URI object @@ -110,7 +105,8 @@ class Request_line { * * @return A read-only reference to the URI object */ - const URI& get_uri() const noexcept; + template + const URI& uri() const noexcept; /** * @brief Set the URI of the message @@ -118,6 +114,7 @@ class Request_line { * @param uri: * The URI of the message */ + template void set_uri(const URI& uri); /** @@ -125,7 +122,8 @@ class Request_line { * * @return The version of the message */ - Version get_version() const noexcept; + template + Version version() const noexcept; /** * @brief Set the version of the message @@ -133,6 +131,7 @@ class Request_line { * @param version: * The version of the message */ + template void set_version(const Version version) noexcept; /** @@ -168,29 +167,29 @@ class Request_line_error : public std::runtime_error { /**--v----------- Implementation Details -----------v--**/ -template -inline Request_line::Request_line(T&& request) { - if (request.empty() or request.size() < 15 /*<-(15) minimum request length */) { - throw Request_line_error("Invalid request"); - } +template +inline Request_line::Request_line() {} - bool is_canonical_line_ending {false}; +inline Request_line::Request_line(std::experimental::string_view request) { + if (request.empty() or (request.length() < 15) /*<-(15) minimum request length */) { + throw Request_line_error{"Invalid request: " + request.to_string()}; + } // Extract {Request-Line} from request - std::string request_line; std::size_t index; + std::experimental::string_view request_line; + + bool is_canonical_line_ending {false}; - if ((index = request.find("\r\n")) not_eq std::string::npos) { + if ((index = request.find("\r\n")) not_eq std::experimental::string_view::npos) { request_line = request.substr(0, index); is_canonical_line_ending = true; - } else if ((index = request.find('\n')) not_eq std::string::npos) { + } else if ((index = request.find('\n')) not_eq std::experimental::string_view::npos) { request_line = request.substr(0, index); } else { - throw Request_line_error("Invalid line-ending"); + throw Request_line_error{"Invalid line-ending"}; } - // Should identify strings according to RFC 2616 sect.5.1 - // https://tools.ietf.org/html/rfc2616#section-5.1 const static std::regex request_line_pattern { "\\s*(GET|POST|PUT|DELETE|OPTIONS|HEAD|TRACE|CONNECT) " // Method @@ -198,13 +197,13 @@ inline Request_line::Request_line(T&& request) { "HTTP/(\\d+)\\.(\\d+)" // Version Major.Minor }; - std::smatch m; + std::cmatch m; - if (not std::regex_match(request_line, m, request_line_pattern)) { - throw Request_line_error("Invalid request line: " + request_line); + if (not std::regex_match(request_line.data(), request_line.data() + request_line.length(), m, request_line_pattern)) { + throw Request_line_error{"Invalid request line: " + request_line.to_string()}; } - method_ = method::code(m[1]); + method_ = method::code(m[1].str()); new (&uri_) URI(m[2]); @@ -220,30 +219,37 @@ inline Request_line::Request_line(T&& request) { } } -inline Method Request_line::get_method() const noexcept { +template +inline Method Request_line::method() const noexcept { return method_; } +template inline void Request_line::set_method(const Method method) noexcept { method_ = method; } -inline URI& Request_line::get_uri() noexcept { +template +inline URI& Request_line::uri() noexcept { return uri_; } -inline const URI& Request_line::get_uri() const noexcept { +template +inline const URI& Request_line::uri() const noexcept { return uri_; } +template inline void Request_line::set_uri(const URI& uri) { new (&uri_) URI(uri); } -inline Version Request_line::get_version() const noexcept { +template +inline Version Request_line::version() const noexcept { return version_; } +template inline void Request_line::set_version(const Version version) noexcept { version_ = version; } diff --git a/inc/response.hpp b/inc/response.hpp index 7eb3b95..b52b841 100644 --- a/inc/response.hpp +++ b/inc/response.hpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -45,28 +45,23 @@ class Response : public Message { * @param version: * The version of the message */ + template explicit Response(const Code code = OK, const Version version = Version{}) noexcept; /** - * @brief Construct a response message from the - * incoming character stream of data which is a - * {std::string} object + * @brief Construct a response message from the incoming character + * stream of data * - * @tparam T response: + * @param response: * The character stream of data * + * @param len: + * The length of the character stream + * * @param limit: * Capacity of how many fields can be added to the header section */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - explicit Response(T&& response, const Limit limit = 100); + explicit Response(std::string response, const Limit limit = 25); /** * @brief Default copy constructor @@ -98,6 +93,7 @@ class Response : public Message { * * @return The status code of this message */ + template Code status_code() const noexcept; /** @@ -108,6 +104,7 @@ class Response : public Message { * * @return The object that invoked this method */ + template Response& set_status_code(const Code code) noexcept; /** @@ -117,7 +114,7 @@ class Response : public Message { * @return The object that invoked this method */ virtual Response& reset() noexcept override; - + /** * @brief Get a string representation of this * class @@ -135,34 +132,39 @@ class Response : public Message { private: //------------------------------ // Class data members + std::string response_; Status_line status_line_; //------------------------------ }; //< class Response /**--v----------- Implementation Details -----------v--**/ +template inline Response::Response(const Code code, const Version version) noexcept : status_line_{version, code} {} -template -inline Response::Response(Egress&& response, const Limit limit) +inline Response::Response(std::string response, const Limit limit) : Message{limit} - , status_line_{response} + , response_{std::move(response)} { - add_headers(response); - std::size_t start_of_body; - if ((start_of_body = response.find("\r\n\r\n")) not_eq std::string::npos) { - add_body(response.substr(start_of_body + 4)); - } else if ((start_of_body = response.find("\n\n")) not_eq std::string::npos) { - add_body(response.substr(start_of_body + 2)); + std::experimental::string_view response_view {response_}; + status_line_ = Status_line{response_view}; + header().add_fields(response_view); + std::experimental::string_view::size_type start_of_body; + if ((start_of_body = response_view.find("\r\n\r\n")) not_eq std::experimental::string_view::npos) { + add_body(response_view.substr(start_of_body + 4U)); + } else if ((start_of_body = response_view.find("\n\n")) not_eq std::experimental::string_view::npos) { + add_body(response_view.substr(start_of_body + 2U)); } } +template inline Response::Code Response::status_code() const noexcept { - return static_cast(status_line_.get_code()); + return static_cast(status_line_.code()); } +template inline Response& Response::set_status_code(const Code code) noexcept { status_line_.set_code(code); return *this; @@ -186,15 +188,38 @@ inline Response::operator std::string () const { return to_string(); } -inline Response& operator << (Response& res, const Header_set& headers) { +/** + * @brief Convenience function to add a set of header fields to the + * specified Response object + * + * @param response + * The Response object to add the header fields to + * + * @param headers + * The set of headers to add to the header section of the Response + * object + * + * @return The specified Response object + */ +template +inline Response& operator << (Response& response, const Header_set& headers) { for (const auto& field : headers) { - res.add_header(field.first, field.second); + response.header().add_field(field.first, field.second); } - return res; + return response; } -inline Response_ptr make_response(buffer_t buf, const size_t len) { - return std::make_unique(std::string{reinterpret_cast(buf.get()), len}); +/** + * @brief Get a std::unique_ptr to a Response object + * + * @param response + * A character stream of data representing an HTTP response message + * + * @return A std::unique_ptr to a Response object + */ +template +inline Response_ptr make_response(std::string response) { + return std::make_unique(std::move(response)); } inline std::ostream& operator << (std::ostream& output_device, const Response& res) { diff --git a/inc/status_codes.hpp b/inc/status_codes.hpp index 268acbe..6320188 100644 --- a/inc/status_codes.hpp +++ b/inc/status_codes.hpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,6 +18,7 @@ #ifndef HTTP_STATUS_CODES_HPP #define HTTP_STATUS_CODES_HPP +#include #include #include "status_code_constants.hpp" @@ -25,7 +26,7 @@ namespace http { //------------------------------------------------ using Code = int; -using Description = const char*; +using Description = std::experimental::string_view; using Status_code_table = std::unordered_map; //------------------------------------------------ const Status_code_table status_codes { @@ -99,28 +100,33 @@ const Status_code_table status_codes { }; //< status_codes inline Description code_description(const Code code) noexcept { - auto iter = status_codes.find(code); - return (iter not_eq status_codes.end()) ? iter->second : "Internal Server Error"; + const auto iter = status_codes.find(code); + return (iter not_eq status_codes.cend()) ? iter->second : "Internal Server Error"; } +template inline bool is_informational(const status_t status_code) noexcept { - return (status_code >= Continue) && (status_code <= Processing); + return (status_code >= Continue) and (status_code <= Processing); } +template inline bool is_success(const status_t status_code) noexcept { - return (status_code >= OK) && (status_code <= IM_Used); + return (status_code >= OK) and (status_code <= IM_Used); } +template inline bool is_redirection(const status_t status_code) noexcept { - return (status_code >= Multiple_Choices) && (status_code <= Permanent_Redirect); + return (status_code >= Multiple_Choices) and (status_code <= Permanent_Redirect); } +template inline bool is_client_error(const status_t status_code) noexcept { - return (status_code >= Bad_Request) && (status_code <= Request_Header_Fields_Too_Large); + return (status_code >= Bad_Request) and (status_code <= Request_Header_Fields_Too_Large); } +template inline bool is_server_error(const status_t status_code) noexcept { - return (status_code >= Internal_Server_Error) && (status_code <= Network_Authentication_Required); + return (status_code >= Internal_Server_Error) and (status_code <= Network_Authentication_Required); } } //< namespace http diff --git a/inc/status_line.hpp b/inc/status_line.hpp index 2e97dac..1a2f60c 100644 --- a/inc/status_line.hpp +++ b/inc/status_line.hpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,15 +26,20 @@ namespace http { /** - * @brief This class respresents a + * @brief This class respresents a * response message status-line */ class Status_line { public: + /** + * @brief Default constructor + */ + explicit constexpr Status_line() noexcept = default; + /** * @brief Constructor to create the status line - * by supplying the version of the message - * and the status code + * by supplying the version of the message and + * the status code * * @param version: * The version of the message @@ -42,25 +47,17 @@ class Status_line { * @param code: * The status code */ + template explicit constexpr Status_line(const Version version, const Code code) noexcept; /** * @brief Constructor to construct a status-line - * from the incoming character stream of - * data which is a {std::string} object + * from the incoming character stream of data * - * @tparam T response: + * @param response: * The character stream of data */ - template - < - typename T, - typename = std::enable_if_t - >>::value> - > - explicit Status_line(T&& response); + explicit Status_line(std::experimental::string_view response); /** * @brief Default copy constructor @@ -92,7 +89,8 @@ class Status_line { * * @return Version of the message */ - constexpr Version get_version() const noexcept; + template + constexpr Version version() const noexcept; /** * @brief Set the version of the message @@ -100,6 +98,7 @@ class Status_line { * @param version: * Version of the message */ + template void set_version(const Version version) noexcept; /** @@ -107,7 +106,8 @@ class Status_line { * * @return Status code of the message */ - constexpr Code get_code() const noexcept; + template + constexpr Code code() const noexcept; /** * @brief Set the message status code @@ -115,6 +115,7 @@ class Status_line { * @param code: * Status code of the message */ + template void set_code(const Code code) noexcept; /** @@ -133,8 +134,8 @@ class Status_line { private: //--------------------------- // Class data members - Version version_; - Code code_; + Version version_ {1U, 1U}; + Code code_ {http::OK}; //--------------------------- }; //< class Status_line @@ -148,27 +149,27 @@ class Status_line_error : public std::runtime_error { /**--v----------- Implementation Details -----------v--**/ +template inline constexpr Status_line::Status_line(const Version version, const Code code) noexcept : version_{version} , code_{code} {} -template -inline Status_line::Status_line(Response&& response) { - if (response.empty() or response.size() < 16 /*<-(16) minimum response length */) { - throw Status_line_error {"Invalid response"}; +inline Status_line::Status_line(std::experimental::string_view response) { + if (response.empty() or (response.length() < 16) /*<-(16) minimum response length */) { + throw Status_line_error {"Invalid response: " + response.to_string()}; } bool is_canonical_line_ending {false}; // Extract {Status-Line} from response - std::string status_line; std::size_t index; + std::experimental::string_view status_line; - if ((index = response.find("\r\n")) not_eq std::string::npos) { + if ((index = response.find("\r\n")) not_eq std::experimental::string_view::npos) { status_line = response.substr(0, index); is_canonical_line_ending = true; - } else if ((index = response.find('\n')) not_eq std::string::npos) { + } else if ((index = response.find('\n')) not_eq std::experimental::string_view::npos) { status_line = response.substr(0, index); } else { throw Status_line_error {"Invalid line-ending"}; @@ -181,10 +182,10 @@ inline Status_line::Status_line(Response&& response) { "[a-z A-Z]+" //< Response Code Description }; - std::smatch m; + std::cmatch m; - if (not std::regex_match(status_line, m, status_line_pattern)) { - throw Status_line_error {"Invalid response line: " + status_line}; + if (not std::regex_match(status_line.data(), status_line.data() + status_line.length(), m, status_line_pattern)) { + throw Status_line_error {"Invalid response line: " + status_line.to_string()}; } version_ = Version(std::stoi(m[1]), std::stoi(m[2])); @@ -199,18 +200,22 @@ inline Status_line::Status_line(Response&& response) { } } -inline constexpr Version Status_line::get_version() const noexcept { +template +inline constexpr Version Status_line::version() const noexcept { return version_; } +template inline void Status_line::set_version(const Version version) noexcept { version_ = version; } -inline constexpr Code Status_line::get_code() const noexcept { +template +inline constexpr Code Status_line::code() const noexcept { return code_; } +template inline void Status_line::set_code(const Code code) noexcept { code_ = code; } diff --git a/inc/time.hpp b/inc/time.hpp index ccd511e..0ac62f6 100644 --- a/inc/time.hpp +++ b/inc/time.hpp @@ -16,9 +16,10 @@ // limitations under the License. #include -#include -#include +#include #include +#include +#include namespace http { namespace time { @@ -58,23 +59,23 @@ inline std::string from_time_t(const std::time_t time) { * * @note Returns a default initialized {time_t} object if an error occurred */ -inline std::time_t to_time_t(const std::string& time) { +inline std::time_t to_time_t(const std::experimental::string_view time) { std::tm tm {}; if (time.empty()) goto error; // Format: Sun, 06 Nov 1994 08:49:37 GMT - if (strptime(time.c_str(), "%a, %d %b %Y %H:%M:%S %Z", &tm) not_eq nullptr) { + if (strptime(time.data(), "%a, %d %b %Y %H:%M:%S %Z", &tm) not_eq nullptr) { return std::mktime(&tm); } // Format: Sunday, 06-Nov-94 08:49:37 GMT - if (strptime(time.c_str(), "%a, %d-%b-%y %H:%M:%S %Z", &tm) not_eq nullptr) { + if (strptime(time.data(), "%a, %d-%b-%y %H:%M:%S %Z", &tm) not_eq nullptr) { return std::mktime(&tm); } // Format: Sun Nov 6 08:49:37 1994 - if(strptime(time.c_str(), "%a %b %d %H:%M:%S %Y", &tm) not_eq nullptr) { + if(strptime(time.data(), "%a %b %d %H:%M:%S %Y", &tm) not_eq nullptr) { return std::mktime(&tm); } @@ -90,8 +91,7 @@ inline std::time_t to_time_t(const std::string& time) { * @note Returns an empty string if an error occurred */ inline std::string now() { - auto time_object = std::time(nullptr); - return from_time_t(time_object); + return from_time_t(std::time(nullptr)); } } //< namespace time diff --git a/inc/version.hpp b/inc/version.hpp index b8a0b63..ccffc6b 100644 --- a/inc/version.hpp +++ b/inc/version.hpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,7 +18,6 @@ #ifndef HTTP_VERSION_HPP #define HTTP_VERSION_HPP -#include #include namespace http { @@ -38,7 +37,7 @@ class Version { * @param minor: * The minor version number */ - explicit constexpr Version(const unsigned major = 1, const unsigned minor = 1) noexcept; + explicit constexpr Version(const unsigned major = 1U, const unsigned minor = 1U) noexcept; /** * @brief Default destructor @@ -70,30 +69,38 @@ class Version { * * @return The major version number */ - constexpr unsigned get_major() const noexcept; + template + constexpr unsigned major() const noexcept; /** * @brief Set the major version number * * @param major: * The major version number + * + * @return The object that invoked this method */ - void set_major(const unsigned major) noexcept; + template + Version& set_major(const unsigned major) noexcept; /** * @brief Get the minor version number * * @return The minor version number */ - constexpr unsigned get_minor() const noexcept; + template + constexpr unsigned minor() const noexcept; /** * @brief Set the minor version number * * @param minor: * The minor version number + * + * @return The object that invoked this method */ - void set_minor(const unsigned minor) noexcept; + template + Version& set_minor(const unsigned minor) noexcept; /** * @brief Get a string representation of this @@ -123,20 +130,26 @@ inline constexpr Version::Version(const unsigned major, const unsigned minor) no , minor_{minor} {} -inline constexpr unsigned Version::get_major() const noexcept { +template +inline constexpr unsigned Version::major() const noexcept { return major_; } -inline void Version::set_major(const unsigned major) noexcept { +template +inline Version& Version::set_major(const unsigned major) noexcept { major_ = major; + return *this; } -inline constexpr unsigned Version::get_minor() const noexcept { +template +inline constexpr unsigned Version::minor() const noexcept { return minor_; } -inline void Version::set_minor(const unsigned minor) noexcept { +template +inline Version& Version::set_minor(const unsigned minor) noexcept { minor_ = minor; + return *this; } inline std::string Version::to_string() const { @@ -173,48 +186,54 @@ inline std::ostream& operator << (std::ostream& output_device, const Version& ve /** * @brief Operator to check for equality */ -inline bool operator == (const Version& lhs, const Version& rhs) noexcept { - return lhs.get_major() == rhs.get_major() +template +inline constexpr bool operator == (const Version lhs, const Version rhs) noexcept { + return lhs.major() == rhs.major() and - lhs.get_minor() == rhs.get_minor(); + lhs.minor() == rhs.minor(); } /** * @brief Operator to check for inequality */ -inline bool operator != (const Version& lhs, const Version& rhs) noexcept { +template +inline constexpr bool operator != (const Version lhs, const Version rhs) noexcept { return not (lhs == rhs); } /** * @brief Operator to check for less than relationship */ -inline bool operator < (const Version& lhs, const Version& rhs) noexcept { - return lhs.get_major() < rhs.get_major() +template +inline constexpr bool operator < (const Version lhs, const Version rhs) noexcept { + return lhs.major() < rhs.major() or - lhs.get_minor() < rhs.get_minor(); + lhs.minor() < rhs.minor(); } /** * @brief Operator to check for greater than relationship */ -inline bool operator > (const Version& lhs, const Version& rhs) noexcept { - return lhs.get_major() > rhs.get_major() +template +inline constexpr bool operator > (const Version lhs, const Version rhs) noexcept { + return lhs.major() > rhs.major() or - lhs.get_minor() > rhs.get_minor(); + lhs.minor() > rhs.minor(); } /** * @brief Operator to check for less than or equal to relationship */ -inline bool operator <= (const Version& lhs, const Version& rhs) noexcept { +template +inline constexpr bool operator <= (const Version lhs, const Version rhs) noexcept { return (lhs < rhs) or (lhs == rhs); } /** * @brief Operator to check for greater than or equal to relationship */ -inline bool operator >= (const Version& lhs, const Version& rhs) noexcept { +template +inline constexpr bool operator >= (const Version lhs, const Version rhs) noexcept { return (lhs > rhs) or (lhs == rhs); } diff --git a/tests/Makefile b/tests/Makefile index 61eefd0..a75622c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -6,32 +6,56 @@ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -CPP=$(shell command -v clang++ || command -v clang++-3.8 || command -v clang++-3.7 || command -v clang++-3.6) -CFLAGS=-std=c++14 -Ofast -Wall -Wextra -INC=-I. -I../inc -I../uri/include -I../uri/GSL/include +CXX=$(shell command -v clang++-3.9 || command -v clang++-3.8 || command -v clang++) +CXXFLAGS=-std=c++14 -Ofast -Wall -Wextra +INC=-I. -I../inc -I../uri/include -I../uri/GSL SRC=../uri/src/percent_encoding.cpp ../uri/src/uri.cpp -all: request response - +all: mime_types status_codes version methods header message status_line \ + request_line request response + +mime_types: mime_types_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -omime_types mime_types_test.cpp test_machine.o + +status_codes: status_codes_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -ostatus_codes status_codes_test.cpp test_machine.o + +version: version_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -oversion version_test.cpp test_machine.o + +methods: methods_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -omethods methods_test.cpp test_machine.o + +header: header_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -oheader header_test.cpp test_machine.o + +message: message_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -omessage message_test.cpp test_machine.o + +status_line: status_line_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -ostatus_line status_line_test.cpp test_machine.o + +request_line: request_line_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -orequest_line request_line_test.cpp test_machine.o $(SRC) + request: request_test.cpp test_machine.o - $(CPP) $(CFLAGS) $(INC) -orequest request_test.cpp test_machine.o $(SRC) + $(CXX) $(CXXFLAGS) $(INC) -orequest request_test.cpp test_machine.o $(SRC) response: response_test.cpp test_machine.o - $(CPP) $(CFLAGS) $(INC) -oresponse response_test.cpp test_machine.o + $(CXX) $(CXXFLAGS) $(INC) -oresponse response_test.cpp test_machine.o test_machine.o: test_machine.cpp - $(CPP) $(CFLAGS) $(INC) -c test_machine.cpp + $(CXX) $(CXXFLAGS) $(INC) -c test_machine.cpp clean: - rm -f request - rm -f response - rm -f test_machine.o + rm -f mime_types status_codes version methods header message status_line \ + request_line request response diff --git a/tests/catch.hpp b/tests/catch.hpp index 879fc5b..3d18ead 100644 --- a/tests/catch.hpp +++ b/tests/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v1.5.6 - * Generated: 2016-06-09 19:20:41.460328 + * Catch v1.5.9 + * Generated: 2016-11-29 12:14:38.049276 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -3223,10 +3223,11 @@ namespace Catch { bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match - for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; - return true; + } + return true; } }; @@ -3427,6 +3428,7 @@ namespace Catch { #include #include #include +#include namespace Catch { @@ -3994,9 +3996,12 @@ namespace Clara { inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } + char toLowerCh(char c) { + return static_cast( ::tolower( c ) ); + } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) @@ -4719,8 +4724,11 @@ namespace Catch { std::string line; while( std::getline( f, line ) ) { line = trim(line); - if( !line.empty() && !startsWith( line, "#" ) ) - addTestOrTags( config, "\"" + line + "\"," ); + if( !line.empty() && !startsWith( line, "#" ) ) { + if( !startsWith( line, "\"" ) ) + line = "\"" + line + "\""; + addTestOrTags( config, line + "," ); + } } } @@ -5368,7 +5376,10 @@ namespace Catch { ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - Catch::cout() << testCaseInfo.name << std::endl; + if( startsWith( testCaseInfo.name, "#" ) ) + Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl; + else + Catch::cout() << testCaseInfo.name << std::endl; } return matchedTests; } @@ -6454,7 +6465,7 @@ namespace Catch { namespace Catch { struct RandomNumberGenerator { - typedef int result_type; + typedef std::ptrdiff_t result_type; result_type operator()( result_type n ) const { return std::rand() % n; } @@ -7571,7 +7582,7 @@ namespace Catch { return os; } - Version libraryVersion( 1, 5, 6, "", 0 ); + Version libraryVersion( 1, 5, 9, "", 0 ); } @@ -7802,8 +7813,11 @@ namespace Catch { bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } + char toLowerCh(char c) { + return static_cast( ::tolower( c ) ); + } void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; @@ -8951,9 +8965,10 @@ namespace Catch { break; default: - // Escape control chars - based on contribution by @espenalb in PR #465 + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) - os << "&#x" << std::uppercase << std::hex << static_cast( c ); + os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast( c ) << ';'; else os << c; } @@ -9008,13 +9023,20 @@ namespace Catch { : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &Catch::cout() ) - {} + { + // We encode control characters, which requires + // XML 1.1 + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + *m_os << "\n"; + } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &os ) - {} + { + *m_os << "\n"; + } ~XmlWriter() { while( !m_tags.empty() ) @@ -9148,6 +9170,7 @@ namespace Catch { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), + m_xml(_config.stream()), m_sectionDepth( 0 ) { m_reporterPrefs.shouldRedirectStdOut = true; @@ -9167,7 +9190,6 @@ namespace Catch { virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); - m_xml.setStream( stream ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); @@ -9181,7 +9203,7 @@ namespace Catch { virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); @@ -9243,7 +9265,7 @@ namespace Catch { .writeText( assertionResult.getMessage() ); break; case ResultWas::FatalErrorCondition: - m_xml.scopedElement( "Fatal Error Condition" ) + m_xml.scopedElement( "FatalErrorCondition" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); diff --git a/tests/header_test.cpp b/tests/header_test.cpp new file mode 100644 index 0000000..a58ab28 --- /dev/null +++ b/tests/header_test.cpp @@ -0,0 +1,139 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Default constructor limits capacity to 25 fields", "[Header]") { + http::Header header; + REQUIRE(25U == header.limit()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Constructor to specify field limit", "[Header]") { + http::Header header {100U}; + REQUIRE(100U == header.limit()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Default constructed object is empty", "[Header]") { + http::Header header; + REQUIRE(true == header.is_empty()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Add a field", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + REQUIRE(false == header.is_empty()); + REQUIRE(1U == header.size()); + REQUIRE(true == header.has_field(http::header_fields::server)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get field value", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + REQUIRE(true == header.has_field(http::header_fields::server)); + REQUIRE("IncludeOS/Acorn v0.1" == header.value(http::header_fields::server)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set field value", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + REQUIRE("IncludeOS/Acorn v0.1" == header.value(http::header_fields::server)); + header.set_field(http::header_fields::server, "IncludeOS/Acorn v2.0"); + REQUIRE("IncludeOS/Acorn v2.0" == header.value(http::header_fields::server)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Erase field", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + header.erase(http::header_fields::server); + REQUIRE(false == header.has_field(http::header_fields::server)); + REQUIRE(true == header.is_empty()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Cannot add fields beyond limit", "[Header]") { + http::Header header {3}; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + header.add_field(http::header_fields::allow, "GET, HEAD"); + header.add_field(http::header_fields::location, "/public/doc/unikernels.pdf"); + header.add_field(http::header_fields::connection, "close"); + + const std::experimental::string_view test_string { + "Server: IncludeOS/Acorn v0.1\r\n" + "Allow: GET, HEAD\r\n" + "Location: /public/doc/unikernels.pdf\r\n\r\n" + }; + ; + REQUIRE(3 == header.size()); + REQUIRE(test_string == header.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Clear all fields", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + header.add_field(http::header_fields::connection, "close"); + REQUIRE(2U == header.size()); + header.clear(); + REQUIRE(0U == header.size()); + REQUIRE(true == header.is_empty()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Header to std::string conversion", "[Header]") { + http::Header header; + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + header.add_field(http::header_fields::allow, "GET, HEAD"); + header.add_field(http::header_fields::connection, "close"); + + const std::experimental::string_view test_string { + "Server: IncludeOS/Acorn v0.1\r\n" + "Allow: GET, HEAD\r\n" + "Connection: close\r\n\r\n" + }; + ; + REQUIRE(test_string == header.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Add fields from a character stream", "[Header]") { + const std::experimental::string_view test_string { + "Server: IncludeOS/Acorn v0.1\r\n" + "Allow: GET, HEAD\r\n" + "Connection: close\r\n\r\n" + }; + + http::Header header {test_string}; + REQUIRE(test_string == header.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Nonesense character stream gets rejected", "[Header]") { + const std::experimental::string_view test_string { + "[IncludeOS] A minimal, resource efficient unikernel for cloud services" + }; + + http::Header header {test_string}; + REQUIRE(true == header.is_empty()); +} diff --git a/tests/message_test.cpp b/tests/message_test.cpp new file mode 100644 index 0000000..5a8bef9 --- /dev/null +++ b/tests/message_test.cpp @@ -0,0 +1,69 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Default constructed message is blank", "[Message]") { + const http::Message message; + REQUIRE("" == message.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get header section", "[Message]") { + http::Message message; + auto& header = message.header(); + header.add_field(http::header_fields::server, "IncludeOS/Acorn v0.1"); + header.add_field(http::header_fields::allow, "GET, HEAD"); + header.add_field(http::header_fields::connection, "close"); + + const std::experimental::string_view test_string { + "Server: IncludeOS/Acorn v0.1\r\n" + "Allow: GET, HEAD\r\n" + "Connection: close\r\n\r\n" + }; + ; + REQUIRE(test_string == message.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Add body", "[Message]") { + http::Message message; + message.add_body("[IncludeOS] A minimal, resource efficient unikernel for cloud services"); + + const std::experimental::string_view test_string { + "Content-Length: 70\r\n\r\n" + "[IncludeOS] A minimal, resource efficient unikernel for cloud services" + }; + + REQUIRE(test_string == message.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Append to body", "[Message]") { + http::Message message; + message.add_body("[IncludeOS] A minimal, resource efficient unikernel for cloud services") + .append_body(" http://www.includeos.org"); + + const std::experimental::string_view test_string { + "Content-Length: 95\r\n\r\n" + "[IncludeOS] A minimal, resource efficient unikernel for cloud services http://www.includeos.org" + }; + + REQUIRE(test_string == message.to_string()); +} diff --git a/tests/methods_test.cpp b/tests/methods_test.cpp new file mode 100644 index 0000000..c97309c --- /dev/null +++ b/tests/methods_test.cpp @@ -0,0 +1,55 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Valid HTTP code to string conversion", "[Methods]") { + const std::string test_string {"GET"}; + REQUIRE(test_string == http::method::str(http::GET)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid HTTP code to string conversion", "[Methods]") { + const std::string test_string {"INVALID"}; + REQUIRE(test_string == http::method::str(static_cast(600))); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Valid HTTP method string to code conversion", "[Methods]") { + REQUIRE(http::GET == http::method::code("GET")); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid HTTP method string to code conversion", "[Methods]") { + REQUIRE(http::INVALID == http::method::code("CALL")); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("HTTP method require Content-Length header field", "[Methods]") { + REQUIRE(true == http::method::is_content_length_required(http::PUT)); + REQUIRE(true == http::method::is_content_length_required(http::POST)); + REQUIRE(false == http::method::is_content_length_required(http::HEAD)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("HTTP method allowed Content-Length header field", "[Methods]") { + REQUIRE(true == http::method::is_content_length_allowed(http::PUT)); + REQUIRE(true == http::method::is_content_length_allowed(http::POST)); + REQUIRE(false == http::method::is_content_length_allowed(http::HEAD)); +} diff --git a/tests/mime_types_test.cpp b/tests/mime_types_test.cpp new file mode 100644 index 0000000..d4ee3d0 --- /dev/null +++ b/tests/mime_types_test.cpp @@ -0,0 +1,29 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Extension that exist within the table", "[Mime_type_table]") { + REQUIRE(http::ext_to_mime_type("mpg") == "video/mpeg"); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Extension that doesn't exist within the table", "[Mime_type_table]") { + REQUIRE(http::ext_to_mime_type("zpr") == "application/octet-stream"); +} diff --git a/tests/request_line_test.cpp b/tests/request_line_test.cpp new file mode 100644 index 0000000..6018458 --- /dev/null +++ b/tests/request_line_test.cpp @@ -0,0 +1,106 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Default constructor", "[Request_line]") { + const http::Request_line request_line; + const std::experimental::string_view test_string {"GET / HTTP/1.1\r\n"}; + REQUIRE(test_string == request_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Constructor taking a character stream", "[Request_line]") { + const std::experimental::string_view test_string { + "GET / HTTP/1.1\r\n" + "Host: 98.139.183.24\r\n" + "Accept: text/html\r\n" + "Connection: close\r\n\r\n" + }; + + const http::Request_line request_line {test_string}; + const std::experimental::string_view result {"GET / HTTP/1.1\r\n"}; + REQUIRE(result == request_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Nonesense character stream throws", "[Request_line]") { + const std::experimental::string_view test_string { + "[IncludeOS] A minimal, resource efficient unikernel for cloud services" + }; + + REQUIRE_THROWS_AS(http::Request_line{test_string}, http::Request_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Empty character stream throws", "[Request_line]") { + REQUIRE_THROWS_AS(http::Request_line{""}, http::Request_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid status line throws [Missing HTTP version]", "[Request_line]") { + const std::experimental::string_view test_string {"GET / \n"}; + REQUIRE_THROWS_AS(http::Request_line{test_string}, http::Request_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid line ending throws", "[Request_line]") { + const std::experimental::string_view test_string {"GET / HTTP/1.1\r"}; + REQUIRE_THROWS_AS(http::Request_line{test_string}, http::Request_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get method", "[Request_line]") { + const http::Request_line request_line; + REQUIRE(http::GET == request_line.method()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set method", "[Request_line]") { + http::Request_line request_line; + request_line.set_method(http::POST); + REQUIRE(http::POST == request_line.method()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get version", "[Request_line]") { + const http::Request_line request_line; + REQUIRE((http::Version{1U, 1U} == request_line.version())); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set version", "[Request_line]") { + http::Request_line request_line; + request_line.set_version(http::Version{2U, 0U}); + REQUIRE((http::Version{2U, 0U} == request_line.version())); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get uri", "[Request_line]") { + const http::Request_line request_line; + REQUIRE(uri::URI{"/"} == request_line.uri()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set uri", "[Request_line]") { + http::Request_line request_line; + const uri::URI uri {"http://includeos.org"}; + request_line.set_uri(uri); + REQUIRE(uri == request_line.uri()); +} diff --git a/tests/request_test.cpp b/tests/request_test.cpp index a3dcf1c..6da2c49 100644 --- a/tests/request_test.cpp +++ b/tests/request_test.cpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,90 +21,74 @@ #define CRLF "\r\n" using namespace std; -using namespace http; /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Default constructor only creates request line", "[Request]") { - Request request; - //------------------------- - string test_string = "GET / HTTP/1.1" CRLF CRLF; - //------------------------- + const http:: Request request; + const std::string test_string {"GET / HTTP/1.1" CRLF}; REQUIRE(test_string == request.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Constructor to parse a character stream", "[Request]") { - string ingress = "GET https://github.com/hioa-cs/IncludeOS HTTP/1.1" CRLF - "Connection: close" CRLF CRLF; - //------------------------- - Request request {std::move(ingress)}; - //------------------------- - REQUIRE(request.method() == GET); - REQUIRE(request.uri().to_string() == "https://github.com/hioa-cs/IncludeOS"); - REQUIRE(request.version() == Version(1, 1)); - REQUIRE(request.has_header("Connection"s) == true); - REQUIRE(request.header_value("Connection"s) == "close"); + const std::string test_string { + "GET https://github.com/hioa-cs/IncludeOS HTTP/1.1" CRLF + "Connection: close" CRLF CRLF + }; + + http::Request request {std::move(test_string)}; + + REQUIRE(http::GET == request.method()); + REQUIRE(true == request.header().has_field(http::header_fields::connection)); + REQUIRE("close" == request.header().value(http::header_fields::connection)); + REQUIRE((http::Version{1, 1} == request.version())); + REQUIRE("https://github.com/hioa-cs/IncludeOS" == request.uri().to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Building a request", "[Request]") { - Request request; - //------------------------- - request.add_header("Host"s, "includeos.server:8080"s) - .add_header("Accept"s, "text/html"s) - .add_header("Connection"s, "close"s); - //------------------------- - string test_string = "GET / HTTP/1.1" CRLF - "Host: includeos.server:8080" CRLF - "Accept: text/html" CRLF - "Connection: close" CRLF CRLF; - //------------------------- + http::Request request; + + request.header().add_field(http::header_fields::host, "includeos.server:8080"); + request.header().add_field(http::header_fields::accept, "text/html"); + request.header().add_field(http::header_fields::connection, "close"); + + const std::string test_string { + "GET / HTTP/1.1" CRLF + "Host: includeos.server:8080" CRLF + "Accept: text/html" CRLF + "Connection: close" CRLF CRLF + }; + REQUIRE(test_string == request.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Handling query data", "[Request]") { - string ingress = "GET includeos.net/q?file=install.sh&machine=x86_64 HTTP/1.1" CRLF - "Host: includeos.server:8080" CRLF - "Connection: close" CRLF CRLF; - //------------------------- - Request request {std::move(ingress)}; - //------------------------- - REQUIRE(request.query_value("file"s) == "install.sh"); - REQUIRE(request.query_value("machine"s) == "x86_64"); + const std::string test_string { + "GET includeos.net/q?file=install.sh&machine=x86_64 HTTP/1.1" CRLF + "Host: includeos.server:8080" CRLF + "Connection: close" CRLF CRLF + }; + + http::Request request {std::move(test_string)}; + + REQUIRE("x86_64" == request.query_value("machine")); + REQUIRE("install.sh" == request.query_value("file")); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Handling post data", "[Request]") { - string ingress = "POST / HTTP/1.1" CRLF - "Host: includeos.server:8080" CRLF - "Connection: close" CRLF CRLF - "name=rico&language=cpp&project=includeos"; - //------------------------- - Request request {std::move(ingress)}; - //------------------------- - REQUIRE(request.post_value("name"s) == "rico"); - REQUIRE(request.post_value("language"s) == "cpp"); - REQUIRE(request.post_value("project"s) == "includeos"); -} + const std::string test_string { + "POST / HTTP/1.1" CRLF + "Host: includeos.server:8080" CRLF + "Connection: close" CRLF CRLF + "name=rico&language=cpp&project=includeos" + }; -/////////////////////////////////////////////////////////////////////////////// -TEST_CASE("Header value folding", "[Request]") { - string ingress = "GET / HTTP/1.1" CRLF - "Host: includeos.server:8080" CRLF - "Client: curl/7.68" CRLF - "Accept: text/plain;q=0.2," CRLF - " text/html;q=0.9," CRLF - " */*;q=0.1" CRLF - "Connection: close" CRLF CRLF; - //------------------------- - Request request {std::move(ingress)}; - //------------------------- - string test_string = "GET / HTTP/1.1" CRLF - "Host: includeos.server:8080" CRLF - "Client: curl/7.68" CRLF - "Accept: text/plain;q=0.2, text/html;q=0.9, */*;q=0.1" CRLF - "Connection: close" CRLF CRLF; - //------------------------- - REQUIRE(test_string == request.to_string()); + http::Request request {std::move(test_string)}; + + REQUIRE("cpp" == request.post_value("language")); + REQUIRE("rico" == request.post_value("name")); + REQUIRE("includeos" == request.post_value("project")); } diff --git a/tests/response_test.cpp b/tests/response_test.cpp index 4480c09..1bab9f1 100644 --- a/tests/response_test.cpp +++ b/tests/response_test.cpp @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,33 +21,30 @@ #define CRLF "\r\n" using namespace std; -using namespace http::header_fields; /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Default constructor only creates status line", "[Response]") { - http::Response response; - //------------------------- - string test_string = "HTTP/1.1 200 OK" CRLF CRLF; - //------------------------- + const http::Response response; + const std::string test_string {"HTTP/1.1 200 OK" CRLF}; REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Get status code", "[Response]") { - http::Response response; - //------------------------- - REQUIRE(response.status_code() == http::status_t::OK); + const http::Response response; + REQUIRE(http::OK == response.status_code()); } /////////////////////////////////////////////////////////////////////////////// SCENARIO("Given a Response object") { http::Response response; - //------------------------- + WHEN("We set it's status code") { - response.set_status_code(http::status_t::Not_Found); - //------------------------- + response.set_status_code(http::Not_Found); + THEN("It should be reflected") { - REQUIRE(response.to_string() == "HTTP/1.1 404 Not Found" CRLF CRLF); + const std::string test_string {"HTTP/1.1 404 Not Found" CRLF}; + REQUIRE(test_string == response.to_string()); } } } @@ -55,84 +52,98 @@ SCENARIO("Given a Response object") { /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Add header field", "[Response]") { http::Response response; - //------------------------- - response.add_header(Response::Server, "IncludeOS/0.7.0"s); - //------------------------- - string test_string = "HTTP/1.1 200 OK" CRLF - "Server: IncludeOS/0.7.0" CRLF CRLF; - //------------------------- + response.header().add_field(http::header_fields::server, "IncludeOS/Acorn v0.7.0"); + + const std::string test_string { + "HTTP/1.1 200 OK" CRLF + "Server: IncludeOS/Acorn v0.7.0" CRLF CRLF + }; + REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("{Date} header field", "[Response]") { http::Response response; - auto time_stamp = http::time::now(); - //------------------------- - response.add_header(Response::Server, "IncludeOS/0.7.0"s) - .add_header("Date"s, time_stamp); - //------------------------- - string test_string = "HTTP/1.1 200 OK"s + "\r\n"s + - "Server: IncludeOS/0.7.0"s + "\r\n"s + - "Date: "s + time_stamp + "\r\n"s + "\r\n"s; - //------------------------- + const auto time_stamp = http::time::now(); + + response.header().add_field(http::header_fields::server, "IncludeOS/Acorn v0.7.0"); + response.header().add_field(http::header_fields::date, time_stamp); + + const std::string test_string { + "HTTP/1.1 200 OK"s + CRLF + + "Server: IncludeOS/Acorn v0.7.0" + CRLF + + "Date: " + time_stamp + CRLF + CRLF + }; + REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Change header field value", "[Response]") { http::Response response; - //------------------------- - response.add_header(Entity::Content_Type, "text/plain"s) - .set_header(Entity::Content_Type, "text/html"s); - //------------------------- - string test_string = "HTTP/1.1 200 OK" CRLF - "Content-Type: text/html" CRLF CRLF; - //------------------------- + + response.header().add_field(http::header_fields::content_type, "text/plain"); + response.header().set_field(http::header_fields::content_type, "text/html"); + + const std::string test_string { + "HTTP/1.1 200 OK" CRLF + "Content-Type: text/html" CRLF CRLF + }; + REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Erase header field", "[Response]") { - http::Response response{http::status_t::Bad_Request}; - //------------------------- - response.add_header(Response::Server, "IncludeOS/0.7.0"s) - .erase_header(Response::Server); - //------------------------- - string test_string = "HTTP/1.1 400 Bad Request" CRLF CRLF; - //------------------------- + http::Response response{http::Bad_Request}; + + response.header().add_field(http::header_fields::server, "IncludeOS/Acorn v0.7.0"); + response.header().erase(http::header_fields::server); + + const std::string test_string {"HTTP/1.1 400 Bad Request" CRLF}; + REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Clear headers", "[Response]") { - http::Response response{http::status_t::Bad_Request}; - //------------------------- - response.add_header(Response::Server, "IncludeOS/0.7.0"s) - .add_header(Entity::Content_Type, "text/javascript"s) - .clear_headers(); - //------------------------- - string test_string = "HTTP/1.1 400 Bad Request" CRLF CRLF; - //------------------------- + http::Response response{http::Bad_Request}; + + response.header().add_field(http::header_fields::server, "IncludeOS/Acorn v0.7.0"); + response.header().add_field(http::header_fields::content_type, "text/javascript"); + response.header().clear(); + + const std::string test_string {"HTTP/1.1 400 Bad Request" CRLF}; + REQUIRE(test_string == response.to_string()); } /////////////////////////////////////////////////////////////////////////////// TEST_CASE("Add message body", "[Response]") { http::Response response; - //------------------------- - response.add_header(Response::Server, "IncludeOS/0.7.0"s) - .add_header(Entity::Content_Type, "text/javascript"s); - //------------------------- - string javascript_file = "document.write('Hello from IncludeOS');"; - //------------------------- + + response.header().add_field(http::header_fields::server, "IncludeOS/Acorn v0.7.0"); + response.header().add_field(http::header_fields::content_type, "text/javascript"); + + const std::string javascript_file {"document.write('Hello from IncludeOS');"}; + response.add_body(javascript_file); - //------------------------- - string test_string = "HTTP/1.1 200 OK" CRLF - "Server: IncludeOS/0.7.0" CRLF - "Content-Type: text/javascript" CRLF - "Content-Length: 39" CRLF CRLF - "document.write('Hello from IncludeOS');"; - //------------------------- + + const std::string test_string { + "HTTP/1.1 200 OK" CRLF + "Server: IncludeOS/Acorn v0.7.0" CRLF + "Content-Type: text/javascript" CRLF + "Content-Length: 39" CRLF CRLF + "document.write('Hello from IncludeOS');" + }; + REQUIRE(test_string == response.to_string()); } + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Make Response_ptr", "[Response]") { + const auto response = http::make_response("HTTP/1.1 200 OK\r\n"); + const std::string test_string {"HTTP/1.1 200 OK\r\n"}; + REQUIRE(test_string == response->to_string()); +} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index de38dc6..72d35b1 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -6,9 +6,9 @@ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,6 +21,22 @@ echo "About to run the test suite..."; echo "Building..."; make; echo ""; +echo "Testing mime_types module..."; +./mime_types; +echo "Testing status_codes module..."; +./status_codes; +echo "Testing version module..."; +./version; +echo "Testing methods module..."; +./methods; +echo "Testing header module..."; +./header; +echo "Testing message module..."; +./message; +echo "Testing status_line module..."; +./status_line; +echo "Testing request_line module..."; +./request_line; echo "Testing request module..."; ./request; echo "Testing response module..."; diff --git a/tests/status_codes_test.cpp b/tests/status_codes_test.cpp new file mode 100644 index 0000000..c015553 --- /dev/null +++ b/tests/status_codes_test.cpp @@ -0,0 +1,61 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Valid code", "[Status Codes]") { + const std::string test_string {"Network Authentication Required"}; + REQUIRE(test_string == http::code_description(http::Network_Authentication_Required)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid code", "[Status Codes]") { + const std::string test_string {"Internal Server Error"}; + REQUIRE(test_string == http::code_description(static_cast(-200))); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Information codes", "[Status Codes]") { + REQUIRE(true == http::is_informational(http::Continue)); + REQUIRE(false == http::is_informational(http::OK)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Success codes", "[Status Codes]") { + REQUIRE(true == http::is_success(http::OK)); + REQUIRE(false == http::is_success(http::Continue)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Redirection codes", "[Status Codes]") { + REQUIRE(true == http::is_redirection(http::Temporary_Redirect)); + REQUIRE(false == http::is_redirection(http::Reset_Content)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Client codes", "[Status Codes]") { + REQUIRE(true == http::is_client_error(http::Not_Acceptable)); + REQUIRE(false == http::is_client_error(http::Gateway_Timeout)); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Server codes", "[Status Codes]") { + REQUIRE(true == http::is_server_error(http::Not_Implemented)); + REQUIRE(false == http::is_server_error(http::Use_Proxy)); +} diff --git a/tests/status_line_test.cpp b/tests/status_line_test.cpp new file mode 100644 index 0000000..4802e9e --- /dev/null +++ b/tests/status_line_test.cpp @@ -0,0 +1,102 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Default constructor", "[Status_line]") { + http::Status_line status_line; + const std::experimental::string_view test_string {"HTTP/1.1 200 OK\r\n"}; + REQUIRE(test_string == status_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Constructor specifying version and code", "[Status_line]") { + http::Status_line status_line {http::Version{2U, 0U}, http::Moved_Permanently}; + const std::experimental::string_view test_string {"HTTP/2.0 301 Moved Permanently\r\n"}; + REQUIRE(test_string == status_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Constructor taking a character stream", "[Status_line]") { + const std::experimental::string_view test_string { + "HTTP/2.0 301 Moved Permanently\r\n" + "Server: IncludeOS/Acorn v0.1\r\n" + "Allow: GET, HEAD\r\n" + "Location: /public/doc/unikernels.pdf\r\n\r\n" + }; + + http::Status_line status_line {test_string}; + + const std::experimental::string_view result {"HTTP/2.0 301 Moved Permanently\r\n"}; + REQUIRE(result == status_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Nonesense character stream throws", "[Status_line]") { + const std::experimental::string_view test_string { + "[IncludeOS] A minimal, resource efficient unikernel for cloud services" + }; + + REQUIRE_THROWS_AS(http::Status_line{test_string}, http::Status_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Empty character stream throws", "[Status_line]") { + REQUIRE_THROWS_AS(http::Status_line{""}, http::Status_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid status line throws [Missing code description]", "[Status_line]") { + const std::experimental::string_view test_string {"HTTP/2.0 301\n"}; + REQUIRE_THROWS_AS(http::Status_line{test_string}, http::Status_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Invalid line ending throws", "[Status_line]") { + const std::experimental::string_view test_string {"HTTP/2.0 301 Moved Permanently\r"}; + REQUIRE_THROWS_AS(http::Status_line{test_string}, http::Status_line_error); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set version", "[Status_line]") { + http::Status_line status_line {http::Version{}, http::OK}; + status_line.set_version(http::Version{2U, 0U}); + const std::experimental::string_view test_string {"HTTP/2.0 200 OK\r\n"}; + REQUIRE(test_string == status_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get version", "[Status_line]") { + http::Status_line status_line {http::Version{1U, 1U}, http::OK}; + REQUIRE((http::Version{1U, 1U} == status_line.version())); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Set code", "[Status_line]") { + http::Status_line status_line {http::Version{}, http::OK}; + status_line.set_code(http::Processing); + const std::experimental::string_view test_string {"HTTP/1.1 102 Processing\r\n"}; + REQUIRE(test_string == status_line.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Get code", "[Status_line]") { + http::Status_line status_line {http::Version{1U, 1U}, http::Processing}; + REQUIRE(http::Processing == status_line.code()); +} diff --git a/tests/version_test.cpp b/tests/version_test.cpp new file mode 100644 index 0000000..c01919e --- /dev/null +++ b/tests/version_test.cpp @@ -0,0 +1,75 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +SCENARIO("Given a Version object") { + http::Version version; + + WHEN("We set its major version number") { + version.set_major(2U); + + THEN("It should be reflected") { + REQUIRE(version.major() == 2U); + } + } + + WHEN("We set its minor version number") { + version.set_minor(0U); + + THEN("It should be reflected") { + REQUIRE(version.minor() == 0U); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Convert a Version object to a std::string", "[Version]") { + const http::Version version; + const std::string test_string {"HTTP/1.1"}; + REQUIRE(test_string == version.to_string()); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Equality", "[Version]") { + const http::Version a {2U, 0U}; + const http::Version b {a}; + REQUIRE(a == b); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Inequality", "[Version]") { + const http::Version a; + const http::Version b {2U, 0U}; + REQUIRE(a not_eq b); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Greater than", "[Version]") { + const http::Version a; + const http::Version b {2U, 0U}; + REQUIRE(b > a); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE("Less than", "[Version]") { + const http::Version a; + const http::Version b {2U, 0U}; + REQUIRE(a < b); +}