diff --git a/CMakeLists.txt b/CMakeLists.txt index e63b904..350c4f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ if(USE_SSL) endif() if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -fPIC -std=c++14 -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++14 -pthread") endif() # Set the library output directory diff --git a/include/frnetlib/Http.h b/include/frnetlib/Http.h index 0754abf..0cb3350 100644 --- a/include/frnetlib/Http.h +++ b/include/frnetlib/Http.h @@ -126,11 +126,6 @@ namespace fr */ void set_body(const std::string &body_); - /*! - * Clear current request data - */ - void clear(); - /*! * Returns a reference to a get variable. * Can be used to either set/get the value. @@ -140,7 +135,7 @@ namespace fr * @param key The name of the GET variable * @return A reference to the GET variable */ - std::string &get(const std::string &key); + std::string &get(std::string &&key); /*! * Returns a reference to a POST variable. @@ -151,7 +146,7 @@ namespace fr * @param key The name of the POST variable * @return A reference to the POST variable */ - std::string &post(const std::string &key); + std::string &post(std::string &&key); /*! * Returns a reference to a header. @@ -171,7 +166,7 @@ namespace fr * @param key The name of the GET variable * @return True if it does. False otherwise. */ - bool get_exists(const std::string &key) const; + bool get_exists(std::string &&key) const; /*! * Checks to see if a given POST variable exists @@ -179,16 +174,16 @@ namespace fr * @param key The name of the POST variable * @return True if it does. False otherwise. */ - bool post_exists(const std::string &key) const; + bool post_exists(std::string &&key) const; /*! * Checks to see if a given header exists. - * Note: 'key' should be lower case. + * Note: key will be converted to lower case * * @param key The name of the header * @return True if it does. False otherwise. */ - bool header_exists(const std::string &key) const; + bool header_exists(std::string &&key) const; /*! * Returns the requested URI diff --git a/include/frnetlib/HttpRequest.h b/include/frnetlib/HttpRequest.h index d4c6aad..ff30fc9 100644 --- a/include/frnetlib/HttpRequest.h +++ b/include/frnetlib/HttpRequest.h @@ -17,7 +17,13 @@ namespace fr public: //Constructors HttpRequest(); - HttpRequest(HttpRequest &&other) = default; + HttpRequest(HttpRequest &&other)=default; + void operator=(const HttpRequest &other) + { + header_ended = other.header_ended; + last_parsed_character = other.last_parsed_character; + content_length = other.content_length; + } virtual ~HttpRequest() = default; /*! diff --git a/src/Http.cpp b/src/Http.cpp index a7ee938..58f69a7 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -13,8 +13,11 @@ namespace fr const static std::string request_type_strings[Http::RequestType::RequestTypeCount] = {"UNKNOWN", "GET", "POST"}; Http::Http() + : request_type(Unknown), + uri("/"), + status(Ok) { - clear(); + } Http::Http(Http &&o) @@ -69,34 +72,27 @@ namespace fr body = body_; } - void Http::clear() - { - post_data.clear(); - get_data.clear(); - post_data.clear(); - body.clear(); - uri = "/"; - status = Ok; - request_type = Unknown; - } - - std::string &Http::get(const std::string &key) + std::string &Http::get(std::string &&key) { + std::transform(key.begin(), key.end(), key.begin(), ::tolower); return get_data[key]; } - std::string &Http::post(const std::string &key) + std::string &Http::post(std::string &&key) { + std::transform(key.begin(), key.end(), key.begin(), ::tolower); return post_data[key]; } - bool Http::get_exists(const std::string &key) const + bool Http::get_exists(std::string &&key) const { + std::transform(key.begin(), key.end(), key.begin(), ::tolower); return get_data.find(key) != get_data.end(); } - bool Http::post_exists(const std::string &key) const + bool Http::post_exists(std::string &&key) const { + std::transform(key.begin(), key.end(), key.begin(), ::tolower); return post_data.find(key) != post_data.end(); } @@ -189,8 +185,9 @@ namespace fr return header_data[key]; } - bool Http::header_exists(const std::string &key) const + bool Http::header_exists(std::string &&key) const { + std::transform(key.begin(), key.end(), key.begin(), ::tolower); return header_data.find(key) != header_data.end(); } diff --git a/src/HttpRequest.cpp b/src/HttpRequest.cpp index a8fef54..ac4fa69 100644 --- a/src/HttpRequest.cpp +++ b/src/HttpRequest.cpp @@ -134,15 +134,24 @@ namespace fr void HttpRequest::parse_post_body() { + //Find beginning of post data auto post_begin = body.find_first_not_of("\r\n"); if(post_begin == std::string::npos) post_begin = body.find_first_not_of("\n"); - if(post_begin != std::string::npos) - { - auto post = parse_argument_list(body.substr(post_begin, body.size() - post_begin)); - for(auto &c : post) - post_data.emplace(std::move(c.first), std::move(c.second)); - } + + //Find end of post data + auto post_end = body.rfind("\r\n\r\n"); + if(post_end == std::string::npos) + post_end = body.rfind("\n\n"); + + //Sanity check + if(post_begin == post_end || post_begin == std::string::npos) + return; + + //Split up the body and store each argument name and value + auto post = parse_argument_list(body.substr(post_begin, body.size() - post_begin - (body.size() - post_end))); + for(auto &c : post) + post_data.emplace(std::move(c.first), std::move(c.second)); } void HttpRequest::parse_header_type(const std::string &str) diff --git a/tests/HttpRequestTest.cpp b/tests/HttpRequestTest.cpp new file mode 100644 index 0000000..9087d41 --- /dev/null +++ b/tests/HttpRequestTest.cpp @@ -0,0 +1,105 @@ +#include "gtest/gtest.h" +#include + +TEST(HttpRequestTest, get_request_parse) +{ + //The test request to parse + const std::string raw_request = + "GET /index.html HTTP/1.1\n" + "Host: frednicolson.co.uk\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "My-Header: header1\n" + "My-Other-Header:header2\r\n" + "Cache-Control: no-cache\r\n\r\n"; + + //Parse it + fr::HttpRequest request; + ASSERT_EQ(request.parse(raw_request.c_str(), raw_request.size()), false); + + //Check that the request type is intact + ASSERT_EQ(request.get_type(), fr::Http::Get); + + //Test that URI is intact + ASSERT_EQ(request.get_uri(), "/index.html"); + + //Test that headers exist + ASSERT_EQ(request.header_exists("host"), true); + ASSERT_EQ(request.header_exists("contEnt-type"), true); + ASSERT_EQ(request.header_exists("my-HeadEr"), true); + ASSERT_EQ(request.header_exists("my-other-header"), true); + ASSERT_EQ(request.header_exists("cache-control"), true); + ASSERT_EQ(request.header_exists("non-existant"), false); + + //Check that headers are intact + ASSERT_EQ(request.header("host"), "frednicolson.co.uk"); + ASSERT_EQ(request.header("content-type"), "application/x-www-form-urlencoded"); + ASSERT_EQ(request.header("My-Other-Header"), "header2"); + ASSERT_EQ(request.header("My-Header"), "header1"); +} + +TEST(HttpRequestTest, post_request_parse) +{ + const std::string raw_request = + "POST /index.html HTTP/1.1\r\n" + "\r\n" + "post_data=data1&some_more_post_data=data23\r\n\r\n"; + + //Parse it + fr::HttpRequest request; + ASSERT_EQ(request.parse(raw_request.c_str(), raw_request.size()), false); + + //Check that the request type is intact + ASSERT_EQ(request.get_type(), fr::Http::Post); + + //Test that URI is intact + ASSERT_EQ(request.get_uri(), "/index.html"); + + //Parse code is the same for GET, so skip header checks. Test if POST data exists. + ASSERT_EQ(request.post_exists("post_data"), true); + ASSERT_EQ(request.post_exists("some_mOre_posT_data"), true); + ASSERT_EQ(request.post_exists("non_existant"), false); + + //Check that the POST data is valid + ASSERT_EQ(request.post("post_dAta"), "data1"); + ASSERT_EQ(request.post("some_more_post_data"), "data23"); +} + +TEST(HttpRequestTest, request_type_parse) +{ + const std::string get_request = "GET / HTTP/1.1\r\n\r\n"; + const std::string post_request = "POST / HTTP/1.1\r\n\r\n"; + const std::string put_request = "PUT / HTTP/1.1\r\n\r\n"; + const std::string delete_request = "DELETE / HTTP/1.1\r\n\r\n"; + const std::string patch_request = "PATCH / HTTP/1.1\r\n\r\n"; + const std::string invalid_request = "INVALID / HTTP/1.1\r\n\r\n"; + + fr::HttpRequest request; + request.parse(get_request.c_str(), get_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Get); + request = {}; + + request.parse(post_request.c_str(), post_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Post); + request = {}; + + request.parse(put_request.c_str(), put_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Put); + request = {}; + + request.parse(delete_request.c_str(), delete_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Delete); + request = {}; + + request.parse(patch_request.c_str(), patch_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Patch); + request = {}; + + request.parse(invalid_request.c_str(), invalid_request.size()); + ASSERT_EQ(request.get_type(), fr::Http::Unknown); + request = {}; +} + +TEST(HttpRequestTest, request_construction) +{ + //todo: more tests +} \ No newline at end of file