From ba4eec7ebe4caee85dc0b30c077c51922fb1b105 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 21 Jun 2017 23:48:59 +0200 Subject: [PATCH] Created Utility.hpp and moved some code into this file. Also improved parse_query_string. --- CMakeLists.txt | 2 +- client_http.hpp | 54 +++++------------------ crypto.hpp | 7 ++- server_http.hpp | 76 ++++--------------------------- tests/parse_test.cpp | 10 +++++ utility.hpp | 103 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 116 deletions(-) create mode 100644 utility.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c94399b..58775de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,4 +49,4 @@ endif() enable_testing() add_subdirectory(tests) -install(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp DESTINATION include/simple-web-server) +install(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp utility.hpp DESTINATION include/simple-web-server) diff --git a/client_http.hpp b/client_http.hpp index d7f1228..a03afa0 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -1,15 +1,14 @@ #ifndef CLIENT_HTTP_HPP #define CLIENT_HTTP_HPP -#include #include #include #include #include +#include "utility.hpp" #ifdef USE_STANDALONE_ASIO #include -#include #include namespace SimpleWeb { using error_code = std::error_code; @@ -31,38 +30,7 @@ namespace SimpleWeb { } #endif -# ifndef CASE_INSENSITIVE_EQUAL_AND_HASH -# define CASE_INSENSITIVE_EQUAL_AND_HASH namespace SimpleWeb { - inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) { - return str1.size() == str2.size() && - std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); - } - class CaseInsensitiveEqual { - public: - bool operator()(const std::string &str1, const std::string &str2) const { - return case_insensitive_equal(str1, str2); - } - }; - // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 - class CaseInsensitiveHash { - public: - size_t operator()(const std::string &str) const { - size_t h = 0; - std::hash hash; - for (auto c : str) - h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); - return h; - } - }; -} -# endif - -namespace SimpleWeb { - using Header = std::unordered_multimap; - template class Client; @@ -77,7 +45,7 @@ namespace SimpleWeb { std::istream content; - Header header; + CaseInsensitiveMultimap header; private: asio::streambuf content_buffer; @@ -143,7 +111,7 @@ namespace SimpleWeb { virtual ~ClientBase() {} /// Synchronous request. The io_service is run within this function. - std::shared_ptr request(const std::string& method, const std::string& path=std::string("/"), string_view content="", const Header& header=Header()) { + std::shared_ptr request(const std::string& method, const std::string& path=std::string("/"), string_view content="", const CaseInsensitiveMultimap& header=CaseInsensitiveMultimap()) { auto session=std::make_shared(io_service, get_connection(), create_request_header(method, path, header)); std::shared_ptr response; session->callback=[this, &response, session](const error_code &ec) { @@ -170,7 +138,7 @@ namespace SimpleWeb { } /// Synchronous request. The io_service is run within this function. - std::shared_ptr request(const std::string& method, const std::string& path, std::iostream& content, const Header& header=Header()) { + std::shared_ptr request(const std::string& method, const std::string& path, std::iostream& content, const CaseInsensitiveMultimap& header=CaseInsensitiveMultimap()) { auto session=std::make_shared(io_service, get_connection(), create_request_header(method, path, header)); std::shared_ptr response; session->callback=[this, &response, session](const error_code &ec) { @@ -202,7 +170,7 @@ namespace SimpleWeb { } /// Asynchronous request where setting and/or running Client's io_service is required. - void request(const std::string &method, const std::string &path, string_view content, const Header& header, + void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap& header, std::function, const error_code&)> &&request_callback_) { auto session=std::make_shared(io_service, get_connection(), create_request_header(method, path, header)); auto request_callback=std::make_shared, const error_code&)>>(std::move(request_callback_)); @@ -228,22 +196,22 @@ namespace SimpleWeb { /// Asynchronous request where setting and/or running Client's io_service is required. void request(const std::string &method, const std::string &path, string_view content, std::function, const error_code&)> &&request_callback) { - request(method, path, content, Header(), std::move(request_callback)); + request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); } /// Asynchronous request where setting and/or running Client's io_service is required. void request(const std::string &method, const std::string &path, std::function, const error_code&)> &&request_callback) { - request(method, path, std::string(), Header(), std::move(request_callback)); + request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback)); } /// Asynchronous request where setting and/or running Client's io_service is required. void request(const std::string &method, std::function, const error_code&)> &&request_callback) { - request(method, std::string("/"), std::string(), Header(), std::move(request_callback)); + request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback)); } /// Asynchronous request where setting and/or running Client's io_service is required. - void request(const std::string &method, const std::string &path, std::iostream& content, const Header& header, + void request(const std::string &method, const std::string &path, std::iostream& content, const CaseInsensitiveMultimap& header, std::function, const error_code&)> &&request_callback_) { auto session=std::make_shared(io_service, get_connection(), create_request_header(method, path, header)); auto request_callback=std::make_shared, const error_code&)>>(std::move(request_callback_)); @@ -274,7 +242,7 @@ namespace SimpleWeb { /// Asynchronous request where setting and/or running Client's io_service is required. void request(const std::string &method, const std::string &path, std::iostream& content, std::function, const error_code&)> &&request_callback) { - request(method, path, content, Header(), std::move(request_callback)); + request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); } protected: @@ -314,7 +282,7 @@ namespace SimpleWeb { virtual std::shared_ptr create_connection()=0; - std::unique_ptr create_request_header(const std::string& method, const std::string& path, const Header& header) const { + std::unique_ptr create_request_header(const std::string& method, const std::string& path, const CaseInsensitiveMultimap& header) const { auto corrected_path=path; if(corrected_path=="") corrected_path="/"; diff --git a/crypto.hpp b/crypto.hpp index 268950d..41910cf 100644 --- a/crypto.hpp +++ b/crypto.hpp @@ -1,5 +1,5 @@ -#ifndef CRYPTO_HPP -#define CRYPTO_HPP +#ifndef SIMPLE_WEB_SERVER_CRYPTO_HPP +#define SIMPLE_WEB_SERVER_CRYPTO_HPP #include #include @@ -216,5 +216,4 @@ namespace SimpleWeb { } }; } -#endif /* CRYPTO_HPP */ - +#endif /* SIMPLE_WEB_SERVER_CRYPTO_HPP */ diff --git a/server_http.hpp b/server_http.hpp index eb5f0d4..0cbcf89 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -2,11 +2,11 @@ #define SERVER_HTTP_HPP #include -#include #include #include #include #include +#include "utility.hpp" #ifdef USE_STANDALONE_ASIO #include @@ -26,35 +26,6 @@ namespace SimpleWeb { } #endif -# ifndef CASE_INSENSITIVE_EQUAL_AND_HASH -# define CASE_INSENSITIVE_EQUAL_AND_HASH -namespace SimpleWeb { - inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) { - return str1.size() == str2.size() && - std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); - } - class CaseInsensitiveEqual { - public: - bool operator()(const std::string &str1, const std::string &str2) const { - return case_insensitive_equal(str1, str2); - } - }; - // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 - class CaseInsensitiveHash { - public: - size_t operator()(const std::string &str) const { - size_t h = 0; - std::hash hash; - for (auto c : str) - h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); - return h; - } - }; -} -# endif - // Late 2017 TODO: remove the following checks and always use std::regex #ifdef USE_BOOST_REGEX #include @@ -68,17 +39,6 @@ namespace SimpleWeb { } #endif -// TODO when switching to c++14, use [[deprecated]] instead -#ifndef DEPRECATED -#ifdef __GNUC__ -#define DEPRECATED __attribute__((deprecated)) -#elif defined(_MSC_VER) -#define DEPRECATED __declspec(deprecated) -#else -#define DEPRECATED -#endif -#endif - namespace SimpleWeb { template class Server; @@ -133,7 +93,7 @@ namespace SimpleWeb { Content content; - std::unordered_multimap header; + CaseInsensitiveMultimap header; regex::smatch path_match; @@ -141,32 +101,12 @@ namespace SimpleWeb { unsigned short remote_endpoint_port; /// Returns query keys with percent-decoded values. - std::unordered_multimap parse_query_string() { - std::unordered_multimap result; - auto qs_start_pos = path.find('?'); - if (qs_start_pos != std::string::npos && qs_start_pos + 1 < path.size()) { - ++qs_start_pos; - static regex::regex pattern("([\\w+%]+)=?([^&]*)"); - int submatches[] = {1, 2}; - auto it_begin = regex::sregex_token_iterator(path.begin() + qs_start_pos, path.end(), pattern, submatches); - auto it_end = regex::sregex_token_iterator(); - for (auto it = it_begin; it != it_end; ++it) { - auto submatch1=it->str(); - auto submatch2=(++it)->str(); - auto query_it = result.emplace(submatch1, submatch2); - auto &value = query_it->second; - for (size_t c = 0; c < value.size(); ++c) { - if (value[c] == '+') - value[c] = ' '; - else if (value[c] == '%' && c + 2 < value.size()) { - auto hex = value.substr(c + 1, 2); - auto chr = static_cast(std::strtol(hex.c_str(), nullptr, 16)); - value.replace(c, 3, &chr, 1); - } - } - } - } - return result; + CaseInsensitiveMultimap parse_query_string() { + auto pos = path.find('?'); + if (pos != std::string::npos && pos + 1 < path.size()) + return SimpleWeb::parse_query_string(path.substr(pos + 1)); + else + return CaseInsensitiveMultimap(); } private: diff --git a/tests/parse_test.cpp b/tests/parse_test.cpp index 948730e..2c74356 100644 --- a/tests/parse_test.cpp +++ b/tests/parse_test.cpp @@ -146,6 +146,16 @@ int main() { auto queries = request.parse_query_string(); assert(queries.empty()); } + { + request.path = "/?="; + auto queries = request.parse_query_string(); + assert(queries.empty()); + } + { + request.path = "/?=test"; + auto queries = request.parse_query_string(); + assert(queries.empty()); + } { request.path = "/?a=1%202%20%203&b=3+4&c&d=æ%25ø%26å%3F"; auto queries = request.parse_query_string(); diff --git a/utility.hpp b/utility.hpp new file mode 100644 index 0000000..ff8d306 --- /dev/null +++ b/utility.hpp @@ -0,0 +1,103 @@ +#ifndef SIMPLE_WEB_SERVER_UTILITY_HPP +#define SIMPLE_WEB_SERVER_UTILITY_HPP + +#include +#include + +// TODO when switching to c++14, use [[deprecated]] instead +#ifndef DEPRECATED +#ifdef __GNUC__ +#define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED __declspec(deprecated) +#else +#define DEPRECATED +#endif +#endif + +namespace SimpleWeb { +#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH +#define CASE_INSENSITIVE_EQUAL_AND_HASH +inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) { + return str1.size() == str2.size() && + std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { + return tolower(a) == tolower(b); + }); +} +class CaseInsensitiveEqual { +public: + bool operator()(const std::string &str1, const std::string &str2) const { + return case_insensitive_equal(str1, str2); + } +}; +// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 +class CaseInsensitiveHash { +public: + size_t operator()(const std::string &str) const { + size_t h = 0; + std::hash hash; + for(auto c : str) + h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } +}; +#endif + +typedef std::unordered_multimap CaseInsensitiveMultimap; + +/// Percent encoding and decoding +class Percent { +public: + static std::string decode(const std::string &value) { + std::string result; + + for(size_t i = 0; i < value.size(); ++i) { + auto &chr = value[i]; + if(chr == '%' && i + 2 < value.size()) { + auto hex = value.substr(i + 1, 2); + auto decoded_chr = static_cast(std::strtol(hex.c_str(), nullptr, 16)); + result += decoded_chr; + i += 2; + } + else if(chr == '+') + result += ' '; + else + result += chr; + } + + return result; + } +}; + +/// Returns query keys with percent-decoded values. +inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) { + CaseInsensitiveMultimap result; + + if(query_string.empty()) + return result; + + size_t parameter_pos = 0; + size_t parameter_end_pos = -1; + size_t value_pos = -1; + for(size_t c = 0; c < query_string.size() + 1; ++c) { + if(query_string[c] == '&' || c == query_string.size()) { + auto parameter = query_string.substr(parameter_pos, (parameter_end_pos == static_cast(-1) ? c : parameter_end_pos) - parameter_pos); + if(!parameter.empty()) { + auto value = value_pos == static_cast(-1) ? std::string() : query_string.substr(value_pos, c - value_pos); + result.emplace(std::move(parameter), Percent::decode(value)); + } + parameter_pos = c + 1; + parameter_end_pos = -1; + value_pos = -1; + } + else if(query_string[c] == '=') { + parameter_end_pos = c; + value_pos = c + 1; + } + } + + return result; +} +} // namespace SimpleWeb + +#endif // SIMPLE_WEB_SERVER_UTILITY_HPP