diff --git a/.gitignore b/.gitignore index b3f7949..29daf6a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,8 @@ cmake_install.cmake install_manifest.txt *.cmake #Additions to https://github.com/github/gitignore/blob/master/CMake.gitignore -build Testing compile_commands.json -.cache .usages_clang *.crt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c16916..fb8ca62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,37 +24,15 @@ target_link_libraries(simple-web-server INTERFACE Threads::Threads) if(USE_STANDALONE_ASIO) target_compile_definitions(simple-web-server INTERFACE ASIO_STANDALONE) - # There is no canonical way to use Asio from CMake. - # In particular, Asio does not support CMake natively. - # However, Conan and Vcpkg do provide CMake support on their own. - # Prefer the CMake target and fall back to finding asio.hpp. - if(NOT TARGET asio::asio) - find_package(asio) - endif() - if(TARGET asio::asio) - target_link_libraries(simple-web-server INTERFACE asio::asio) + find_path(ASIO_PATH asio.hpp) + if(NOT ASIO_PATH) + message(FATAL_ERROR "Standalone Asio not found") else() - find_path(ASIO_PATH asio.hpp) - if(NOT ASIO_PATH) - message(FATAL_ERROR "Standalone Asio not found") - endif() target_include_directories(simple-web-server SYSTEM INTERFACE ${ASIO_PATH}) endif() else() - # In Boost 1.89.0+, system is header-only, so we try to find it but make it optional - find_package(Boost 1.53.0 COMPONENTS system) - if(NOT Boost_FOUND) - # Fallback: try without system component for newer Boost versions where it's header-only - find_package(Boost 1.53.0 REQUIRED) - endif() - - target_link_libraries(simple-web-server INTERFACE Boost::boost) - - # Only link Boost::system if the target exists (for older Boost < 1.89) - if(TARGET Boost::system) - target_link_libraries(simple-web-server INTERFACE Boost::system) - endif() - + find_package(Boost 1.53.0 COMPONENTS system REQUIRED) + target_link_libraries(simple-web-server INTERFACE Boost::boost Boost::system) 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) @@ -83,7 +61,7 @@ endif() # If Simple-Web-Server is not a sub-project: if(CMAKE_SOURCE_DIR STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") if(NOT MSVC) - add_compile_options(-Wall -Wextra -Wsign-conversion) + add_compile_options(-Wall -Wextra) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wthread-safety) endif() diff --git a/asio_compatibility.hpp b/asio_compatibility.hpp index e93c0cf..aa43451 100644 --- a/asio_compatibility.hpp +++ b/asio_compatibility.hpp @@ -27,18 +27,12 @@ namespace SimpleWeb { #endif namespace SimpleWeb { -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 103300) || BOOST_ASIO_VERSION >= 103300 - using const_buffer = asio::const_buffer; -#else - using const_buffer = asio::const_buffers_1; -#endif - -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 +#if(defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 using io_context = asio::io_context; using resolver_results = asio::ip::tcp::resolver::results_type; using async_connect_endpoint = asio::ip::tcp::endpoint; -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 101800) || BOOST_ASIO_VERSION >= 101800 +#if(defined(ASIO_STANDALONE) && ASIO_VERSION >= 101800) || BOOST_ASIO_VERSION >= 101800 using strand = asio::strand; #else using strand = asio::strand; diff --git a/client_http.hpp b/client_http.hpp index 44f8ca1..e12fcd2 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -17,7 +17,7 @@ namespace SimpleWeb { public: /// Match condition for asio::read_until to match both standard and non-standard HTTP header endings. - std::pair, bool> operator()(asio::buffers_iterator begin, asio::buffers_iterator end) { + std::pair, bool> operator()(asio::buffers_iterator begin, asio::buffers_iterator end) { auto it = begin; for(; it != end; ++it) { if(*it == '\n') { @@ -72,11 +72,11 @@ namespace SimpleWeb { friend class ClientBase; public: - std::size_t size() const noexcept { + std::size_t size() noexcept { return streambuf.size(); } /// Convenience function to return content as a string. - std::string string() const noexcept { + std::string string() noexcept { return std::string(asio::buffers_begin(streambuf.data()), asio::buffers_end(streambuf.data())); } diff --git a/client_https.hpp b/client_https.hpp index 09d37ab..86c042c 100644 --- a/client_https.hpp +++ b/client_https.hpp @@ -27,7 +27,7 @@ namespace SimpleWeb { Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &certification_file = std::string(), const std::string &private_key_file = std::string(), const std::string &verify_file = std::string()) : ClientBase::ClientBase(server_port_path, 443), -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 +#if(defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 context(asio::ssl::context::tls_client) { // Disabling TLS 1.0 and 1.1 (see RFC 8996) context.set_options(asio::ssl::context::no_tlsv1); @@ -41,11 +41,7 @@ namespace SimpleWeb { } if(verify_certificate) -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 103300) || BOOST_ASIO_VERSION >= 103300 - context.set_verify_callback(asio::ssl::host_name_verification(host)); -#else context.set_verify_callback(asio::ssl::rfc2818_verification(host)); -#endif if(verify_file.size() > 0) context.load_verify_file(verify_file); diff --git a/crypto.hpp b/crypto.hpp index be9bfb9..3629598 100644 --- a/crypto.hpp +++ b/crypto.hpp @@ -70,7 +70,7 @@ namespace SimpleWeb { b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // TODO: Remove in 2022 or later -#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x1000214fL) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2080000fL) +#if(defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x1000214fL) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2080000fL) bio = BIO_new_mem_buf(const_cast(&base64[0]), static_cast(base64.size())); #else bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); diff --git a/server_http.hpp b/server_http.hpp index 50f7a86..1db0368 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -135,7 +135,7 @@ namespace SimpleWeb { } public: - std::size_t size() const noexcept { + std::size_t size() noexcept { return streambuf->size(); } @@ -209,11 +209,11 @@ namespace SimpleWeb { friend class ServerBase; public: - std::size_t size() const noexcept { + std::size_t size() noexcept { return streambuf.size(); } /// Convenience function to return content as std::string. - std::string string() const noexcept { + std::string string() noexcept { return std::string(asio::buffers_begin(streambuf.data()), asio::buffers_end(streambuf.data())); } @@ -227,16 +227,11 @@ namespace SimpleWeb { friend class Server; friend class Session; - std::unique_ptr streambuf; - asio::streambuf content_streambuf; + asio::streambuf streambuf; std::weak_ptr connection; std::string optimization = std::to_string(0); // TODO: figure out what goes wrong in gcc optimization without this line - Request(std::size_t max_request_streambuf_size, const std::shared_ptr &connection_) noexcept - : streambuf(new asio::streambuf(max_request_streambuf_size)), content_streambuf(max_request_streambuf_size), connection(connection_), content(content_streambuf) {} - Request(std::size_t max_request_streambuf_size, const std::shared_ptr &connection_, std::unique_ptr &&previous_streambuf) noexcept - : streambuf(std::move(previous_streambuf)), content_streambuf(max_request_streambuf_size), connection(connection_), content(content_streambuf) { - } + Request(std::size_t max_request_streambuf_size, const std::shared_ptr &connection_) noexcept : streambuf(max_request_streambuf_size), connection(connection_), content(streambuf) {} public: std::string method, path, query_string, http_version; @@ -297,10 +292,6 @@ namespace SimpleWeb { CaseInsensitiveMultimap parse_query_string() const noexcept { return SimpleWeb::QueryString::parse(query_string); } - - std::shared_ptr connection_shared() const noexcept { - return connection.lock(); - } }; protected: @@ -356,10 +347,7 @@ namespace SimpleWeb { class Session { public: - Session(std::size_t max_request_streambuf_size, std::shared_ptr connection_) noexcept - : connection(std::move(connection_)), request(new Request(max_request_streambuf_size, connection)) {} - Session(std::size_t max_request_streambuf_size, std::shared_ptr connection_, std::unique_ptr &&previous_streambuf) noexcept - : connection(std::move(connection_)), request(new Request(max_request_streambuf_size, connection, std::move(previous_streambuf))) {} + Session(std::size_t max_request_streambuf_size, std::shared_ptr connection_) noexcept : connection(std::move(connection_)), request(new Request(max_request_streambuf_size, connection)) {} std::shared_ptr connection; std::shared_ptr request; @@ -570,7 +558,7 @@ namespace SimpleWeb { void read(const std::shared_ptr &session) { session->connection->set_timeout(config.timeout_request); - asio::async_read_until(*session->connection->socket, *session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) { + asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; @@ -578,14 +566,13 @@ namespace SimpleWeb { if(!ec) { session->connection->set_timeout(this->config.timeout_content); - // request->streambuf->size() is not necessarily the same as bytes_transferred, from Boost-docs: + // request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs: // "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" // The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the // streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). - std::size_t num_additional_bytes = session->request->streambuf->size() - bytes_transferred; + std::size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred; - std::istream istream(session->request->streambuf.get()); - if(!RequestMessage::parse(istream, session->request->method, session->request->path, + if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path, session->request->query_string, session->request->http_version, session->request->header)) { if(this->on_error) this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); @@ -604,24 +591,15 @@ namespace SimpleWeb { this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); return; } - if(content_length > session->request->streambuf->max_size()) { + if(content_length > session->request->streambuf.max_size()) { auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); response->write(StatusCode::client_error_payload_too_large); if(this->on_error) this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); return; } - - if(num_additional_bytes > 0) { - auto content_bytes = std::min(content_length, (unsigned long long)num_additional_bytes); - auto &source = *session->request->streambuf; - auto &target = session->request->content_streambuf; - target.commit(asio::buffer_copy(target.prepare(content_bytes), source.data(), content_bytes)); - source.consume(content_bytes); - } - if(content_length > num_additional_bytes) { - asio::async_read(*session->connection->socket, session->request->content_streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { + asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; @@ -632,16 +610,15 @@ namespace SimpleWeb { this->on_error(session->request, ec); }); } - else { + else this->find_resource(session); - } } else if((header_it = session->request->header.find("Transfer-Encoding")) != session->request->header.end() && header_it->second == "chunked") { // Expect hex number to not exceed 16 bytes (64-bit number), but take into account previous additional read bytes - auto chunk_size_streambuf = std::make_shared(std::max(16 + 2, session->request->streambuf->size())); + auto chunk_size_streambuf = std::make_shared(std::max(16 + 2, session->request->streambuf.size())); // Move leftover bytes - auto &source = *session->request->streambuf; + auto &source = session->request->streambuf; auto &target = *chunk_size_streambuf; target.commit(asio::buffer_copy(target.prepare(source.size()), source.data())); source.consume(source.size()); @@ -677,7 +654,12 @@ namespace SimpleWeb { return; } - if(chunk_size + session->request->content_streambuf.size() > session->request->content_streambuf.max_size()) { + if(chunk_size == 0) { + this->find_resource(session); + return; + } + + if(chunk_size + session->request->streambuf.size() > session->request->streambuf.max_size()) { auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); response->write(StatusCode::client_error_payload_too_large); if(this->on_error) @@ -685,34 +667,19 @@ namespace SimpleWeb { return; } - auto read_next = [this, chunk_size](const std::shared_ptr &session, const std::shared_ptr &chunk_size_streambuf) { - if(chunk_size == 0) { - if(chunk_size_streambuf->size() > 0) { - // Remaining bytes are the beginning of the next request - auto &source = *chunk_size_streambuf; - auto &target = *session->request->streambuf; - target.commit(asio::buffer_copy(target.prepare(chunk_size_streambuf->size()), source.data(), chunk_size_streambuf->size())); - source.consume(chunk_size_streambuf->size()); - } - this->find_resource(session); - } - else - this->read_chunked_transfer_encoded(session, chunk_size_streambuf); - }; - auto num_additional_bytes = chunk_size_streambuf->size() - bytes_transferred; auto bytes_to_move = std::min(chunk_size, num_additional_bytes); if(bytes_to_move > 0) { // Move leftover bytes auto &source = *chunk_size_streambuf; - auto &target = session->request->content_streambuf; + auto &target = session->request->streambuf; target.commit(asio::buffer_copy(target.prepare(bytes_to_move), source.data(), bytes_to_move)); source.consume(bytes_to_move); } if(chunk_size > num_additional_bytes) { - asio::async_read(*session->connection->socket, session->request->content_streambuf, asio::transfer_exactly(chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf, read_next](const error_code &ec, size_t /*bytes_transferred*/) { + asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf](const error_code &ec, size_t /*bytes_transferred*/) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; @@ -720,12 +687,12 @@ namespace SimpleWeb { if(!ec) { // Remove "\r\n" auto null_buffer = std::make_shared(2); - asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2), [this, session, chunk_size_streambuf, null_buffer, read_next](const error_code &ec, size_t /*bytes_transferred*/) { + asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2), [this, session, chunk_size_streambuf, null_buffer](const error_code &ec, size_t /*bytes_transferred*/) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; if(!ec) - read_next(session, chunk_size_streambuf); + read_chunked_transfer_encoded(session, chunk_size_streambuf); else this->on_error(session->request, ec); }); @@ -739,12 +706,12 @@ namespace SimpleWeb { if(2 + chunk_size - num_additional_bytes == 1) istream.get(); auto null_buffer = std::make_shared(2); - asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2 + chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf, null_buffer, read_next](const error_code &ec, size_t /*bytes_transferred*/) { + asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2 + chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf, null_buffer](const error_code &ec, size_t /*bytes_transferred*/) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; if(!ec) - read_next(session, chunk_size_streambuf); + read_chunked_transfer_encoded(session, chunk_size_streambuf); else this->on_error(session->request, ec); }); @@ -754,7 +721,7 @@ namespace SimpleWeb { istream.get(); istream.get(); - read_next(session, chunk_size_streambuf); + read_chunked_transfer_encoded(session, chunk_size_streambuf); } } else if(this->on_error) @@ -811,13 +778,13 @@ namespace SimpleWeb { if(case_insensitive_equal(it->second, "close")) return; else if(case_insensitive_equal(it->second, "keep-alive")) { - auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection, std::move(response->session->request->streambuf)); + auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); this->read(new_session); return; } } if(response->session->request->http_version >= "1.1") { - auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection, std::move(response->session->request->streambuf)); + auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); this->read(new_session); return; } diff --git a/server_https.hpp b/server_https.hpp index 7a02b68..9b77fdc 100644 --- a/server_https.hpp +++ b/server_https.hpp @@ -29,7 +29,7 @@ namespace SimpleWeb { */ Server(const std::string &certification_file, const std::string &private_key_file, const std::string &verify_file = std::string()) : ServerBase::ServerBase(443), -#if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 +#if(defined(ASIO_STANDALONE) && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 context(asio::ssl::context::tls_server) { // Disabling TLS 1.0 and 1.1 (see RFC 8996) context.set_options(asio::ssl::context::no_tlsv1);