From 0d8caeca153d1597e62e69d884e23b70a6095903 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 7 Nov 2017 11:35:44 +0100 Subject: [PATCH 01/11] Modernised all CMakeLists.txt files, and made it easier to use Simple-Web-Server as a sub-project --- .travis.yml | 2 +- CMakeLists.txt | 89 ++++++++++++++++++++++++++++---------------- tests/CMakeLists.txt | 19 +++------- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61370c3..2860748 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ script: make && CTEST_OUTPUT_ON_FAILURE=1 make test && rm -r * && - CXX=g++ cmake -DCMAKE_CXX_FLAGS=\"-Werror -O3 -DUSE_STANDALONE_ASIO\" .. && + CXX=g++ cmake -DUSE_STANDALONE_ASIO=ON -DCMAKE_CXX_FLAGS=\"-Werror -O3\" .. && make && CTEST_OUTPUT_ON_FAILURE=1 make test " diff --git a/CMakeLists.txt b/CMakeLists.txt index 950bd82..2dc18e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,52 +1,75 @@ cmake_minimum_required (VERSION 2.8.8) -project (Simple-Web-Server) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wsign-conversion") -include_directories(.) +project (Simple-Web-Server) + +option(USE_STANDALONE_ASIO "set ON to use standalone Asio instead of Boost.Asio" OFF) +option(BUILD_TESTING "set ON to build library tests" OFF) + +add_compile_options(-std=c++11 -Wall -Wextra -Wsign-conversion) + +add_library(simple-web-server INTERFACE) + +target_include_directories(simple-web-server INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) find_package(Threads REQUIRED) +target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT}) -set(BOOST_COMPONENTS system filesystem thread) -# Late 2017 TODO: remove the following checks and always use std::regex -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BOOST_REGEX") +if(USE_STANDALONE_ASIO) + target_compile_definitions(simple-web-server INTERFACE USE_STANDALONE_ASIO) + # TODO: Fix this: + # include(CheckIncludeFileCXX) + # CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO) + # if(NOT HAVE_ASIO) + # message(FATAL_ERROR "Standalone Asio not found") + # endif() +else() + find_package(Boost 1.53.0 COMPONENTS system thread REQUIRED) + target_link_libraries(simple-web-server INTERFACE ${Boost_LIBRARIES}) + target_include_directories(simple-web-server INTERFACE ${Boost_INCLUDE_DIR}) + # 2020 TODO: remove the following checks and always use std::regex + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) + target_compile_definitions(simple-web-server INTERFACE USE_BOOST_REGEX) + find_package(Boost 1.53.0 COMPONENTS regex REQUIRED) + target_link_libraries(simple-web-server INTERFACE ${Boost_LIBRARIES}) + target_include_directories(simple-web-server INTERFACE ${Boost_INCLUDE_DIR}) endif() endif() -find_package(Boost 1.53.0 COMPONENTS ${BOOST_COMPONENTS} REQUIRED) -include_directories(SYSTEM ${Boost_INCLUDE_DIR}) +if(WIN32) + target_link_libraries(simple-web-server INTERFACE ws2_32 wsock32) +endif() if(APPLE) set(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") endif() - -add_executable(http_examples http_examples.cpp) -target_link_libraries(http_examples ${Boost_LIBRARIES}) -target_link_libraries(http_examples ${CMAKE_THREAD_LIBS_INIT}) - -#TODO: add requirement for version 1.0.1g (can it be done in one line?) find_package(OpenSSL) - if(OPENSSL_FOUND) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_OPENSSL") - target_link_libraries(http_examples ${OPENSSL_LIBRARIES}) - include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) - - add_executable(https_examples https_examples.cpp) - target_link_libraries(https_examples ${Boost_LIBRARIES}) - target_link_libraries(https_examples ${OPENSSL_LIBRARIES}) - target_link_libraries(https_examples ${CMAKE_THREAD_LIBS_INIT}) + target_compile_definitions(simple-web-server INTERFACE HAVE_OPENSSL) + target_link_libraries(simple-web-server INTERFACE ${OPENSSL_LIBRARIES}) + target_include_directories(simple-web-server INTERFACE ${OPENSSL_INCLUDE_DIR}) endif() -if(MSYS) #TODO: Is MSYS true when MSVC is true? - target_link_libraries(http_examples ws2_32 wsock32) +# if Simple-Web-Server is not a sub-project: +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + add_executable(http_examples http_examples.cpp) + target_link_libraries(http_examples simple-web-server) + + find_package(Boost 1.53.0 COMPONENTS system filesystem REQUIRED) + target_link_libraries(http_examples ${Boost_LIBRARIES}) + target_include_directories(http_examples PRIVATE ${Boost_INCLUDE_DIR}) + if(OPENSSL_FOUND) - target_link_libraries(https_examples ws2_32 wsock32) + add_executable(https_examples https_examples.cpp) + target_link_libraries(https_examples simple-web-server) + target_link_libraries(https_examples ${Boost_LIBRARIES}) + target_include_directories(https_examples PRIVATE ${Boost_INCLUDE_DIR}) endif() + + set(BUILD_TESTING ON) + + install(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp crypto.hpp utility.hpp status_code.hpp DESTINATION include/simple-web-server) endif() -enable_testing() -add_subdirectory(tests) - -install(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp crypto.hpp utility.hpp status_code.hpp DESTINATION include/simple-web-server) +if(BUILD_TESTING) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08fc773..a8a0d45 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,26 +1,19 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-access-control") +add_compile_options(-fno-access-control) add_executable(io_test io_test.cpp) -target_link_libraries(io_test ${Boost_LIBRARIES}) -target_link_libraries(io_test ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(io_test simple-web-server) +add_test(io_test io_test) add_executable(parse_test parse_test.cpp) -target_link_libraries(parse_test ${Boost_LIBRARIES}) -target_link_libraries(parse_test ${CMAKE_THREAD_LIBS_INIT}) - -if(MSYS) #TODO: Is MSYS true when MSVC is true? - target_link_libraries(io_test ws2_32 wsock32) - target_link_libraries(parse_test ws2_32 wsock32) -endif() - -add_test(io_test io_test) +target_link_libraries(parse_test simple-web-server) add_test(parse_test parse_test) if(OPENSSL_FOUND) add_executable(crypto_test crypto_test.cpp) - target_link_libraries(crypto_test ${OPENSSL_CRYPTO_LIBRARY}) + target_link_libraries(crypto_test simple-web-server) add_test(crypto_test crypto_test) endif() add_executable(status_code_test status_code_test.cpp) +target_link_libraries(status_code_test simple-web-server) add_test(status_code_test status_code_test) From 29f8cc56690aa58dd1a43c64a9d2cac903dda63c Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 7 Nov 2017 13:46:42 +0100 Subject: [PATCH 02/11] Further CMakeLists.txt improvements and cleanups --- CMakeLists.txt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dc18e7..f697fd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,19 +14,19 @@ target_include_directories(simple-web-server INTERFACE ${CMAKE_CURRENT_SOURCE_DI find_package(Threads REQUIRED) target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT}) +# TODO 2020 when Debian Jessie LTS ends: +# Remove Boost system, thread, regex components; use Boost:: aliases; remove Boost target_include_directories if(USE_STANDALONE_ASIO) target_compile_definitions(simple-web-server INTERFACE USE_STANDALONE_ASIO) - # TODO: Fix this: - # include(CheckIncludeFileCXX) - # CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO) - # if(NOT HAVE_ASIO) - # message(FATAL_ERROR "Standalone Asio not found") - # endif() + include(CheckIncludeFileCXX) + CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO) + if(NOT HAVE_ASIO) + message(FATAL_ERROR "Standalone Asio not found") + endif() else() find_package(Boost 1.53.0 COMPONENTS system thread REQUIRED) target_link_libraries(simple-web-server INTERFACE ${Boost_LIBRARIES}) target_include_directories(simple-web-server INTERFACE ${Boost_INCLUDE_DIR}) - # 2020 TODO: remove the following checks and always use std::regex if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) target_compile_definitions(simple-web-server INTERFACE USE_BOOST_REGEX) find_package(Boost 1.53.0 COMPONENTS regex REQUIRED) @@ -48,15 +48,13 @@ if(OPENSSL_FOUND) target_include_directories(simple-web-server INTERFACE ${OPENSSL_INCLUDE_DIR}) endif() -# if Simple-Web-Server is not a sub-project: +# If Simple-Web-Server is not a sub-project: if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") add_executable(http_examples http_examples.cpp) target_link_libraries(http_examples simple-web-server) - find_package(Boost 1.53.0 COMPONENTS system filesystem REQUIRED) target_link_libraries(http_examples ${Boost_LIBRARIES}) target_include_directories(http_examples PRIVATE ${Boost_INCLUDE_DIR}) - if(OPENSSL_FOUND) add_executable(https_examples https_examples.cpp) target_link_libraries(https_examples simple-web-server) From f55eb4af6580fe0ada436df0ead090fca53da284 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 7 Nov 2017 18:47:00 +0100 Subject: [PATCH 03/11] Changed ContentDisposition::parse to HttpHeader::FieldValue::SemicolonSeparated::parse. This function can now also parse Set-Cookie header field values --- tests/parse_test.cpp | 82 +++++++++++++++++++++++++++++++--- utility.hpp | 102 ++++++++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 50 deletions(-) diff --git a/tests/parse_test.cpp b/tests/parse_test.cpp index ce91224..38e47f8 100644 --- a/tests/parse_test.cpp +++ b/tests/parse_test.cpp @@ -195,19 +195,89 @@ int main() { { { - SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}}; - auto parsed = SimpleWeb::ContentDisposition::parse("form-data"); + SimpleWeb::CaseInsensitiveMultimap solution; + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse(""); assert(parsed == solution); } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}}; + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a"); + assert(parsed == solution); + } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}, {"b", ""}}; + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a; b"); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a;b"); + assert(parsed == solution); + } + } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}, {"b", "c"}}; + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a; b=c"); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a;b=c"); + assert(parsed == solution); + } + } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}}; + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data"); + assert(parsed == solution); + } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"test", ""}}; + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; test"); + assert(parsed == solution); + } + } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "file"}}; - auto parsed = SimpleWeb::ContentDisposition::parse("form-data; name=\"file\""); - assert(parsed == solution); + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"file\""); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=file"); + assert(parsed == solution); + } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "file"}, {"filename", "filename.png"}}; - auto parsed = SimpleWeb::ContentDisposition::parse("form-data; name=\"file\"; filename=\"filename.png\""); - assert(parsed == solution); + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"file\"; filename=\"filename.png\""); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data;name=\"file\";filename=\"filename.png\""); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=file; filename=filename.png"); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data;name=file;filename=filename.png"); + assert(parsed == solution); + } + } + { + SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "fi le"}, {"filename", "file name.png"}}; + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"fi le\"; filename=\"file name.png\""); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=fi le; filename=file name.png"); + assert(parsed == solution); + } } } } diff --git a/utility.hpp b/utility.hpp index 2af49e7..5925403 100644 --- a/utility.hpp +++ b/utility.hpp @@ -155,7 +155,64 @@ namespace SimpleWeb { } return result; } - }; + + class FieldValue { + public: + class SemicolonSeparated { + public: + /// Parse Set-Cookie or Content-Disposition header field value + static CaseInsensitiveMultimap parse(const std::string &line) { + CaseInsensitiveMultimap result; + + std::size_t para_start_pos = std::string::npos; + std::size_t para_end_pos = std::string::npos; + std::size_t value_start_pos = std::string::npos; + for(std::size_t c = 0; c < line.size(); ++c) { + if(para_start_pos == std::string::npos) { + if(line[c] != ' ' && line[c] != ';') + para_start_pos = c; + } + else { + if(para_end_pos == std::string::npos) { + if(line[c] == ';') { + result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string()); + para_start_pos = std::string::npos; + } + else if(line[c] == '=') + para_end_pos = c; + } + else { + if(value_start_pos == std::string::npos) { + if(line[c] == '"' && c + 1 < line.size()) + value_start_pos = c + 1; + else + value_start_pos = c; + } + else if(line[c] == '"' || line[c] == ';') { + result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos)); + para_start_pos = std::string::npos; + para_end_pos = std::string::npos; + value_start_pos = std::string::npos; + } + } + } + } + if(para_start_pos != std::string::npos) { + if(para_end_pos == std::string::npos) + result.emplace(line.substr(para_start_pos), std::string()); + else if(value_start_pos != std::string::npos) { + if(line.back() == '"') + result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, line.size() - 1)); + else + result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos)); + } + } + + return result; + } + }; + }; + }; // namespace SimpleWeb class RequestMessage { public: @@ -231,49 +288,6 @@ namespace SimpleWeb { return true; } }; - - class ContentDisposition { - public: - /// Can be used to parse the Content-Disposition header field value when - /// clients are posting requests with enctype="multipart/form-data" - static CaseInsensitiveMultimap parse(const std::string &line) { - CaseInsensitiveMultimap result; - - std::size_t para_start_pos = 0; - std::size_t para_end_pos = std::string::npos; - std::size_t value_start_pos = std::string::npos; - for(std::size_t c = 0; c < line.size(); ++c) { - if(para_start_pos != std::string::npos) { - if(para_end_pos == std::string::npos) { - if(line[c] == ';') { - result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string()); - para_start_pos = std::string::npos; - } - else if(line[c] == '=') - para_end_pos = c; - } - else { - if(value_start_pos == std::string::npos) { - if(line[c] == '"' && c + 1 < line.size()) - value_start_pos = c + 1; - } - else if(line[c] == '"') { - result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos)); - para_start_pos = std::string::npos; - para_end_pos = std::string::npos; - value_start_pos = std::string::npos; - } - } - } - else if(line[c] != ' ' && line[c] != ';') - para_start_pos = c; - } - if(para_start_pos != std::string::npos && para_end_pos == std::string::npos) - result.emplace(line.substr(para_start_pos), std::string()); - - return result; - } - }; } // namespace SimpleWeb #ifdef __SSE2__ From 61847f3443d723eda713e3b6a4425efea24e5f40 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 7 Nov 2017 22:17:15 +0100 Subject: [PATCH 04/11] Made Request::remote_endpoint public --- server_http.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server_http.hpp b/server_http.hpp index 97ee7de..d0c60b0 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -183,10 +183,9 @@ namespace SimpleWeb { friend class Session; asio::streambuf streambuf; - std::shared_ptr remote_endpoint; Request(std::size_t max_request_streambuf_size, std::shared_ptr remote_endpoint) noexcept - : streambuf(max_request_streambuf_size), remote_endpoint(std::move(remote_endpoint)), content(streambuf) {} + : streambuf(max_request_streambuf_size), content(streambuf), remote_endpoint(std::move(remote_endpoint)) {} public: std::string method, path, query_string, http_version; @@ -197,6 +196,8 @@ namespace SimpleWeb { regex::smatch path_match; + std::shared_ptr remote_endpoint; + std::string remote_endpoint_address() noexcept { try { return remote_endpoint->address().to_string(); From 702a571451899f903660aa33835cc279051c0b0e Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 8 Nov 2017 09:40:28 +0100 Subject: [PATCH 05/11] Fixes compilation issue for Debian Jessie --- crypto.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crypto.hpp b/crypto.hpp index 2c63133..1a2c8fa 100644 --- a/crypto.hpp +++ b/crypto.hpp @@ -68,7 +68,12 @@ namespace SimpleWeb { b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); +// TODO: Cannot find the exact version this change happended +#if OPENSSL_VERSION_NUMBER <= 0x1000114fL + bio = BIO_new_mem_buf((char *)&base64[0], static_cast(base64.size())); +#else bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); +#endif bio = BIO_push(b64, bio); auto decoded_length = BIO_read(bio, &ascii[0], static_cast(ascii.size())); From 54a3d2ef5a1f7368fdbb393b21c1d2ba09d0cadc Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 8 Nov 2017 09:47:53 +0100 Subject: [PATCH 06/11] Fixes compilation issue on Debian Jessie when option USE_STANDALONE_ASIO is turned on --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f697fd4..c0b1acf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ endif() if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") add_executable(http_examples http_examples.cpp) target_link_libraries(http_examples simple-web-server) - find_package(Boost 1.53.0 COMPONENTS system filesystem REQUIRED) + find_package(Boost 1.53.0 COMPONENTS system thread filesystem REQUIRED) target_link_libraries(http_examples ${Boost_LIBRARIES}) target_include_directories(http_examples PRIVATE ${Boost_INCLUDE_DIR}) if(OPENSSL_FOUND) From c173ef5823da7c4bc15fb08e7946ce2289a0e1f8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 8 Nov 2017 09:56:46 +0100 Subject: [PATCH 07/11] Updated TODO comment in crypto.hpp --- crypto.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto.hpp b/crypto.hpp index 1a2c8fa..8cf7cca 100644 --- a/crypto.hpp +++ b/crypto.hpp @@ -68,7 +68,7 @@ namespace SimpleWeb { b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); -// TODO: Cannot find the exact version this change happended +// TODO: Cannot find the exact version this change happended. Remove in 2020 #if OPENSSL_VERSION_NUMBER <= 0x1000114fL bio = BIO_new_mem_buf((char *)&base64[0], static_cast(base64.size())); #else From 2860f76139e53f4ce0645a46b0e85055db60cad0 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 8 Nov 2017 11:30:21 +0100 Subject: [PATCH 08/11] HttpHeader::FieldValue::SemicolonSeparated renamed to HttpHeader::FieldValue::SemicolonSeparatedAttributes, and cleanup of HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse, attribute values are now also percent-decoded --- tests/parse_test.cpp | 40 ++++++++++++++++++++-------------- utility.hpp | 52 ++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/tests/parse_test.cpp b/tests/parse_test.cpp index 38e47f8..f058a11 100644 --- a/tests/parse_test.cpp +++ b/tests/parse_test.cpp @@ -196,86 +196,94 @@ int main() { { { SimpleWeb::CaseInsensitiveMultimap solution; - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse(""); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse(""); assert(parsed == solution); } { SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}}; - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("a"); assert(parsed == solution); } { SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}, {"b", ""}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a; b"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("a; b"); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a;b"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("a;b"); assert(parsed == solution); } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"a", ""}, {"b", "c"}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a; b=c"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("a; b=c"); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("a;b=c"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("a;b=c"); assert(parsed == solution); } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}}; - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data"); assert(parsed == solution); } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"test", ""}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; test"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; test"); assert(parsed == solution); } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "file"}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"file\""); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=\"file\""); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=file"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=file"); assert(parsed == solution); } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "file"}, {"filename", "filename.png"}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"file\"; filename=\"filename.png\""); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=\"file\"; filename=\"filename.png\""); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data;name=\"file\";filename=\"filename.png\""); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data;name=\"file\";filename=\"filename.png\""); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=file; filename=filename.png"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=file; filename=filename.png"); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data;name=file;filename=filename.png"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data;name=file;filename=filename.png"); assert(parsed == solution); } } { SimpleWeb::CaseInsensitiveMultimap solution = {{"form-data", ""}, {"name", "fi le"}, {"filename", "file name.png"}}; { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=\"fi le\"; filename=\"file name.png\""); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=\"fi le\"; filename=\"file name.png\""); assert(parsed == solution); } { - auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparated::parse("form-data; name=fi le; filename=file name.png"); + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=\"fi%20le\"; filename=\"file%20name.png\""); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=fi le; filename=file name.png"); + assert(parsed == solution); + } + { + auto parsed = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse("form-data; name=fi%20le; filename=file%20name.png"); assert(parsed == solution); } } diff --git a/utility.hpp b/utility.hpp index 5925403..b1b33e2 100644 --- a/utility.hpp +++ b/utility.hpp @@ -158,53 +158,53 @@ namespace SimpleWeb { class FieldValue { public: - class SemicolonSeparated { + class SemicolonSeparatedAttributes { public: - /// Parse Set-Cookie or Content-Disposition header field value - static CaseInsensitiveMultimap parse(const std::string &line) { + /// Parse Set-Cookie or Content-Disposition header field value. Attribute values are percent-decoded. + static CaseInsensitiveMultimap parse(const std::string &str) { CaseInsensitiveMultimap result; - std::size_t para_start_pos = std::string::npos; - std::size_t para_end_pos = std::string::npos; + std::size_t name_start_pos = std::string::npos; + std::size_t name_end_pos = std::string::npos; std::size_t value_start_pos = std::string::npos; - for(std::size_t c = 0; c < line.size(); ++c) { - if(para_start_pos == std::string::npos) { - if(line[c] != ' ' && line[c] != ';') - para_start_pos = c; + for(std::size_t c = 0; c < str.size(); ++c) { + if(name_start_pos == std::string::npos) { + if(str[c] != ' ' && str[c] != ';') + name_start_pos = c; } else { - if(para_end_pos == std::string::npos) { - if(line[c] == ';') { - result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string()); - para_start_pos = std::string::npos; + if(name_end_pos == std::string::npos) { + if(str[c] == ';') { + result.emplace(str.substr(name_start_pos, c - name_start_pos), std::string()); + name_start_pos = std::string::npos; } - else if(line[c] == '=') - para_end_pos = c; + else if(str[c] == '=') + name_end_pos = c; } else { if(value_start_pos == std::string::npos) { - if(line[c] == '"' && c + 1 < line.size()) + if(str[c] == '"' && c + 1 < str.size()) value_start_pos = c + 1; else value_start_pos = c; } - else if(line[c] == '"' || line[c] == ';') { - result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos)); - para_start_pos = std::string::npos; - para_end_pos = std::string::npos; + else if(str[c] == '"' || str[c] == ';') { + result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos, c - value_start_pos))); + name_start_pos = std::string::npos; + name_end_pos = std::string::npos; value_start_pos = std::string::npos; } } } } - if(para_start_pos != std::string::npos) { - if(para_end_pos == std::string::npos) - result.emplace(line.substr(para_start_pos), std::string()); + if(name_start_pos != std::string::npos) { + if(name_end_pos == std::string::npos) + result.emplace(str.substr(name_start_pos), std::string()); else if(value_start_pos != std::string::npos) { - if(line.back() == '"') - result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, line.size() - 1)); + if(str.back() == '"') + result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos, str.size() - 1))); else - result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos)); + result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos))); } } From 647a733251535ef936fdb49430ef33d79110d0e6 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 10 Nov 2017 08:22:33 +0100 Subject: [PATCH 09/11] Optimised the status_code-functions --- status_code.hpp | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/status_code.hpp b/status_code.hpp index 66022a5..51c8dde 100644 --- a/status_code.hpp +++ b/status_code.hpp @@ -1,7 +1,9 @@ #ifndef SIMPLE_WEB_STATUS_CODE_HPP #define SIMPLE_WEB_STATUS_CODE_HPP +#include #include +#include #include namespace SimpleWeb { @@ -138,19 +140,33 @@ namespace SimpleWeb { } inline StatusCode status_code(const std::string &status_code_str) noexcept { - for(auto &status_code : status_codes()) { - if(status_code.second == status_code_str) - return status_code.first; - } - return StatusCode::unknown; + class StringToStatusCode : public std::unordered_map { + public: + StringToStatusCode() { + for(auto &status_code : SimpleWeb::status_codes()) + emplace(status_code.second, status_code.first); + } + }; + static StringToStatusCode string_to_status_code; + auto pos = string_to_status_code.find(status_code_str); + if(pos == string_to_status_code.end()) + return StatusCode::unknown; + return pos->second; } const inline std::string &status_code(StatusCode status_code_enum) noexcept { - for(auto &status_code : status_codes()) { - if(status_code.first == status_code_enum) - return status_code.second; - } - return status_codes()[0].second; + class StatusCodeToString : public std::map { + public: + StatusCodeToString() { + for(auto &status_code : SimpleWeb::status_codes()) + emplace(status_code.first, status_code.second); + } + }; + static StatusCodeToString status_code_to_string; + auto pos = status_code_to_string.find(status_code_enum); + if(pos == status_code_to_string.end()) + return status_codes()[0].second; + return pos->second; } } // namespace SimpleWeb From 5a7c021dab6de484590a45701e8eab29b1a7b718 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 14 Nov 2017 19:44:40 +0100 Subject: [PATCH 10/11] Added MSVC support to cmake files (not tested, but hopefully it works). Some tests are disabled due to lacking MSVC options. --- CMakeLists.txt | 6 +++++- tests/CMakeLists.txt | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c0b1acf..6289957 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,11 @@ project (Simple-Web-Server) option(USE_STANDALONE_ASIO "set ON to use standalone Asio instead of Boost.Asio" OFF) option(BUILD_TESTING "set ON to build library tests" OFF) -add_compile_options(-std=c++11 -Wall -Wextra -Wsign-conversion) +if(NOT MSVC) + add_compile_options(-std=c++11 -Wall -Wextra -Wsign-conversion) +else() + add_compile_options(/W1) +endif() add_library(simple-web-server INTERFACE) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a8a0d45..45fa95e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,12 +1,14 @@ -add_compile_options(-fno-access-control) +if(NOT MSVC) + add_compile_options(-fno-access-control) + + add_executable(io_test io_test.cpp) + target_link_libraries(io_test simple-web-server) + add_test(io_test io_test) -add_executable(io_test io_test.cpp) -target_link_libraries(io_test simple-web-server) -add_test(io_test io_test) - -add_executable(parse_test parse_test.cpp) -target_link_libraries(parse_test simple-web-server) -add_test(parse_test parse_test) + add_executable(parse_test parse_test.cpp) + target_link_libraries(parse_test simple-web-server) + add_test(parse_test parse_test) +endif() if(OPENSSL_FOUND) add_executable(crypto_test crypto_test.cpp) From ccce9d1fdf020668b38ec99279e2ae8bf1698edb Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 15 Nov 2017 07:40:02 +0100 Subject: [PATCH 11/11] Added Request::remote_endpoint tests to io_test --- tests/io_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/io_test.cpp b/tests/io_test.cpp index 84cb212..f1bc4e9 100644 --- a/tests/io_test.cpp +++ b/tests/io_test.cpp @@ -64,6 +64,9 @@ int main() { *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; + + assert(!request->remote_endpoint_address().empty()); + assert(request->remote_endpoint_port() != 0); }; server.resource["^/string2$"]["POST"] = [](shared_ptr response, shared_ptr request) {