diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6a2365..0544fbf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ set(BOOST_HTTP_PROTO_DEPENDENCIES Boost::capy Boost::config Boost::core + Boost::json Boost::mp11 Boost::static_assert Boost::system @@ -122,7 +123,7 @@ if (BOOST_HTTP_PROTO_IS_ROOT) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") else () # From Boost Package - find_package(Boost REQUIRED COMPONENTS buffers filesystem url) + find_package(Boost REQUIRED COMPONENTS buffers filesystem json url) foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) diff --git a/build/Jamfile b/build/Jamfile index b4898e96..8e688b72 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -41,12 +41,14 @@ lib boost_http_proto : requirements /boost//buffers /boost//capy + /boost/json//boost_json/off /boost//url ../ BOOST_HTTP_PROTO_SOURCE : usage-requirements /boost//buffers /boost//capy + /boost/json//boost_json/off /boost//url ; diff --git a/include/boost/http_proto.hpp b/include/boost/http_proto.hpp index f552d00c..de591acd 100644 --- a/include/boost/http_proto.hpp +++ b/include/boost/http_proto.hpp @@ -14,8 +14,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/include/boost/http_proto/detail/file_posix.hpp b/include/boost/http_proto/detail/file_posix.hpp deleted file mode 100644 index caba34f8..00000000 --- a/include/boost/http_proto/detail/file_posix.hpp +++ /dev/null @@ -1,116 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_DETAIL_FILE_POSIX_HPP -#define BOOST_HTTP_PROTO_DETAIL_FILE_POSIX_HPP - -#include - -#if ! defined(BOOST_HTTP_PROTO_NO_POSIX_FILE) -# if ! defined(__APPLE__) && ! defined(__linux__) && ! defined(__FreeBSD__) && ! defined(__NetBSD__) -# define BOOST_HTTP_PROTO_NO_POSIX_FILE -# endif -#endif - -#if ! defined(BOOST_HTTP_PROTO_USE_POSIX_FILE) -# if ! defined(BOOST_HTTP_PROTO_NO_POSIX_FILE) -# define BOOST_HTTP_PROTO_USE_POSIX_FILE 1 -# else -# define BOOST_HTTP_PROTO_USE_POSIX_FILE 0 -# endif -#endif - -#if BOOST_HTTP_PROTO_USE_POSIX_FILE - -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -// Implementation of File for POSIX systems. -class file_posix -{ - int fd_ = -1; - - BOOST_HTTP_PROTO_DECL - static - int - native_close(int& fd); - -public: - using native_handle_type = int; - - BOOST_HTTP_PROTO_DECL - ~file_posix(); - - file_posix() = default; - - BOOST_HTTP_PROTO_DECL - file_posix(file_posix&& other) noexcept; - - BOOST_HTTP_PROTO_DECL - file_posix& - operator=(file_posix&& other) noexcept; - - native_handle_type - native_handle() const - { - return fd_; - } - - BOOST_HTTP_PROTO_DECL - void - native_handle(native_handle_type fd); - - bool - is_open() const - { - return fd_ != -1; - } - - BOOST_HTTP_PROTO_DECL - void - close(system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - void - open(char const* path, file_mode mode, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::uint64_t - size(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - std::uint64_t - pos(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - void - seek(std::uint64_t offset, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - read(void* buffer, std::size_t n, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - write(void const* buffer, std::size_t n, system::error_code& ec); -}; - -} // detail -} // http_proto -} // boost - -#endif - -#endif diff --git a/include/boost/http_proto/detail/file_stdio.hpp b/include/boost/http_proto/detail/file_stdio.hpp deleted file mode 100644 index e6121dc9..00000000 --- a/include/boost/http_proto/detail/file_stdio.hpp +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_DETAIL_FILE_STDIO_HPP -#define BOOST_HTTP_PROTO_DETAIL_FILE_STDIO_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -// Implementation of File which uses cstdio. -class file_stdio -{ - std::FILE* f_ = nullptr; - -public: - using native_handle_type = std::FILE*; - - BOOST_HTTP_PROTO_DECL - ~file_stdio(); - - file_stdio() = default; - - BOOST_HTTP_PROTO_DECL - file_stdio(file_stdio&& other) noexcept; - - BOOST_HTTP_PROTO_DECL - file_stdio& - operator=(file_stdio&& other) noexcept; - - std::FILE* - native_handle() const - { - return f_; - } - - BOOST_HTTP_PROTO_DECL - void - native_handle(std::FILE* f); - - bool - is_open() const - { - return f_ != nullptr; - } - - BOOST_HTTP_PROTO_DECL - void - close(system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - void - open(char const* path, file_mode mode, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::uint64_t - size(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - std::uint64_t - pos(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - void - seek(std::uint64_t offset, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - read(void* buffer, std::size_t n, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - write(void const* buffer, std::size_t n, system::error_code& ec); -}; - -} // detail -} // http_proto -} // boost - -#endif diff --git a/include/boost/http_proto/detail/file_win32.hpp b/include/boost/http_proto/detail/file_win32.hpp deleted file mode 100644 index 21f20e03..00000000 --- a/include/boost/http_proto/detail/file_win32.hpp +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_DETAIL_FILE_WIN32_HPP -#define BOOST_HTTP_PROTO_DETAIL_FILE_WIN32_HPP - -#include - -#if ! defined(BOOST_HTTP_PROTO_USE_WIN32_FILE) -# ifdef _WIN32 -# define BOOST_HTTP_PROTO_USE_WIN32_FILE 1 -# else -# define BOOST_HTTP_PROTO_USE_WIN32_FILE 0 -# endif -#endif - -#if BOOST_HTTP_PROTO_USE_WIN32_FILE - -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -// Implementation of File for Win32. -class file_win32 -{ - boost::winapi::HANDLE_ h_ = - boost::winapi::INVALID_HANDLE_VALUE_; - -public: - using native_handle_type = boost::winapi::HANDLE_; - - BOOST_HTTP_PROTO_DECL - ~file_win32(); - - file_win32() = default; - - BOOST_HTTP_PROTO_DECL - file_win32(file_win32&& other) noexcept; - - BOOST_HTTP_PROTO_DECL - file_win32& - operator=(file_win32&& other) noexcept; - - native_handle_type - native_handle() - { - return h_; - } - - BOOST_HTTP_PROTO_DECL - void - native_handle(native_handle_type h); - - bool - is_open() const - { - return h_ != boost::winapi::INVALID_HANDLE_VALUE_; - } - - BOOST_HTTP_PROTO_DECL - void - close(system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - void - open(char const* path, file_mode mode, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::uint64_t - size(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - std::uint64_t - pos(system::error_code& ec) const; - - BOOST_HTTP_PROTO_DECL - void - seek(std::uint64_t offset, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - read(void* buffer, std::size_t n, system::error_code& ec); - - BOOST_HTTP_PROTO_DECL - std::size_t - write(void const* buffer, std::size_t n, system::error_code& ec); -}; - -} // detail -} // http_proto -} // boost - -#endif - -#endif diff --git a/include/boost/http_proto/file.hpp b/include/boost/http_proto/file.hpp deleted file mode 100644 index c03df376..00000000 --- a/include/boost/http_proto/file.hpp +++ /dev/null @@ -1,394 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// Copyright (c) 2025 Mohammad Nejati -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_FILE_HPP -#define BOOST_HTTP_PROTO_FILE_HPP - -#include -#include -#include -#include -#include -#include -namespace boost { -namespace http_proto { - -/** A platform-independent file stream. - - This class provides a portable interface for - reading from and writing to files. It is - intended for use with @ref file_sink and @ref - file_source to enable streaming HTTP message - bodies to and from files. - - @par Example 1 - @code - file f("example.zip", file_mode::scan); - response.set_payload_size(f.size()); - serializer.start(response, std::move(f)); - @endcode - - @par Example 2 - @code - parser.set_body("example.zip", file_mode::write_new); - @endcode - - @see - @ref file_mode, - @ref file_sink, - @ref file_source. -*/ -class file -{ -#if BOOST_HTTP_PROTO_USE_WIN32_FILE - using impl_type = detail::file_win32; -#elif BOOST_HTTP_PROTO_USE_POSIX_FILE - using impl_type = detail::file_posix; -#else - using impl_type = detail::file_stdio; -#endif - - impl_type impl_; - -public: - /** The type of the underlying native file handle. - - This type is platform-specific. - */ - using native_handle_type = impl_type::native_handle_type; - - /** Constructor. - - There is no open file initially. - */ - file() = default; - - /** Constructor. - - Open a file at the given path with the specified mode. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - - @param path The UTF-8 encoded path to the file. - - @param mode The file mode to use. - - @see - @ref file_mode, - @ref open. - */ - file(char const* path, file_mode mode) - { - open(path, mode); - } - - /** Constructor. - - The moved-from object behaves as if default-constructed. - */ - file(file&& other) noexcept = default; - - /** Assignment - - The moved-from object behaves as if default-constructed. - */ - file& - operator=( - file&& other) noexcept = default; - - /** Destructor - - If the file is open it is first closed. - */ - ~file() = default; - - /** Returns the native handle associated with the file. - */ - native_handle_type - native_handle() - { - return impl_.native_handle(); - } - - /** Set the native file handle. - - If the file is open it is first closed. - - @param h The native handle to assign. - */ - void - native_handle(native_handle_type h) - { - impl_.native_handle(h); - } - - /** Return true if the file is open. - */ - bool - is_open() const - { - return impl_.is_open(); - } - - /** Close the file if open. - - Note that, The descriptor is closed even if the function - reports an error. - - @param ec Set to the error, if any occurred. - */ - void - close(system::error_code& ec) - { - impl_.close(ec); - } - - /** Close the file if open. - - Note that, The descriptor is closed even if the function - reports an error. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - */ - void - close() - { - system::error_code ec; - impl_.close(ec); - if(ec.failed()) - detail::throw_system_error(ec); - } - - /** Open a file at the given path with the specified mode. - - @param path The UTF-8 encoded path to the file. - - @param mode The file mode to use. - - @param ec Set to the error, if any occurred. - - @see - @ref file_mode. - */ - void - open(char const* path, file_mode mode, system::error_code& ec) - { - impl_.open(path, mode, ec); - } - - /** Open a file at the given path with the specified mode. - - @param path The UTF-8 encoded path to the file. - - @param mode The file mode to use. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - - @see - @ref file_mode. - */ - void - open(char const* path, file_mode mode) - { - system::error_code ec; - impl_.open(path, mode, ec); - if(ec.failed()) - detail::throw_system_error(ec); - } - - /** Return the size of the open file in bytes. - - @param ec Set to the error, if any occurred. - */ - std::uint64_t - size(system::error_code& ec) const - { - return impl_.size(ec); - } - - /** Return the size of the open file in bytes. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - */ - std::uint64_t - size() const - { - system::error_code ec; - auto r = impl_.size(ec); - if(ec.failed()) - detail::throw_system_error(ec); - return r; - } - - /** Return the current position in the file, in bytes from the beginning. - - @param ec Set to the error, if any occurred. - */ - std::uint64_t - pos(system::error_code& ec) const - { - return impl_.pos(ec); - } - - /** Return the current position in the file, in bytes from the beginning. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - */ - std::uint64_t - pos() const - { - system::error_code ec; - auto r = impl_.pos(ec); - if(ec.failed()) - detail::throw_system_error(ec); - return r; - } - - /** Set the current position in the file. - - @param offset The byte offset from the beginning of the file. - - @param ec Set to the error, if any occurred. - */ - void - seek(std::uint64_t offset, system::error_code& ec) - { - impl_.seek(offset, ec); - } - - /** Set the current position in the file. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - - @param offset The byte offset from the beginning of the file. - */ - void - seek(std::uint64_t offset) - { - system::error_code ec; - impl_.seek(offset, ec); - if(ec.failed()) - detail::throw_system_error(ec); - } - - /** Read data from the file. - - @return The number of bytes read. Returns - 0 on end-of-file or if an error occurs (in - which case @p ec is set). - - @param buffer The buffer to store the read data. - - @param n The number of bytes to read. - - @param ec Set to the error, if any occurred. - */ - std::size_t - read(void* buffer, std::size_t n, system::error_code& ec) - { - return impl_.read(buffer, n, ec); - } - - /** Read data from the file. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - - @return The number of bytes read. Returns - 0 on end-of-file. - - @param buffer The buffer to store the read data. - - @param n The number of bytes to read. - */ - std::size_t - read(void* buffer, std::size_t n) - { - system::error_code ec; - auto r = impl_.read(buffer, n, ec); - if(ec.failed()) - detail::throw_system_error(ec); - return r; - } - - /** Write data to the file. - - @return The number of bytes written. - Returns 0 on error (in which case @p ec is - set). - - @param buffer The buffer containing the data to write. - - @param n The number of bytes to write. - - @param ec Set to the error, if any occurred. - */ - std::size_t - write(void const* buffer, std::size_t n, system::error_code& ec) - { - return impl_.write(buffer, n, ec); - } - - /** Write data to the file. - - @par Exception Safety - Exception thrown if operation fails. - - @throw system_error - Operation fails. - - @return The number of bytes written. - - @param buffer The buffer containing the data to write. - - @param n The number of bytes to write. - */ - std::size_t - write(void const* buffer, std::size_t n) - { - system::error_code ec; - auto r = impl_.write(buffer, n, ec); - if(ec.failed()) - detail::throw_system_error(ec); - return r; - } -}; - -} // http_proto -} // boost - -#endif diff --git a/include/boost/http_proto/file_mode.hpp b/include/boost/http_proto/file_mode.hpp deleted file mode 100644 index 266a7298..00000000 --- a/include/boost/http_proto/file_mode.hpp +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// Copyright (c) 2025 Mohammad Nejati -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_FILE_MODE_HPP -#define BOOST_HTTP_PROTO_FILE_MODE_HPP - -namespace boost { -namespace http_proto { - -/** File open modes - - These modes are used when opening files using - instances of the @ref file. - - @code - file_mode access sharing seeking file std mode - -------------------------------------------------------------------------------------- - read read-only shared random must exist "rb" - scan read-only shared sequential must exist "rbS" - write read/write exclusive random create/truncate "wb+" - write_new read/write exclusive random must not exist "wbx" - write_existing read/write exclusive random must exist "rb+" - append write-only exclusive sequential create/truncate "ab" - append_existing write-only exclusive sequential must exist "ab" - @endcode - - @see - @ref file. -*/ -enum class file_mode -{ - /** Random read-only access to an existing file. - */ - read, - - /** Sequential read-only access to an existing file. - */ - scan, - - /** Random reading and writing to a new or truncated file. - - This mode permits random-access reading and writing - for the specified file. If the file does not exist - prior to the function call, it is created with an - initial size of zero bytes. Otherwise if the file - already exists, the size is truncated to zero bytes. - */ - write, - - /** Random reading and writing to a new file only. - - This mode permits random-access reading and writing - for the specified file. The file will be created with - an initial size of zero bytes. If the file already exists - prior to the function call, an error is returned and - no file is opened. - */ - write_new, - - /** Random write-only access to existing file. - - If the file does not exist, an error is generated. - */ - write_existing, - - /** Appending to a new or truncated file. - - The current file position shall be set to the end of - the file prior to each write. - - @li If the file does not exist, it is created. - - @li If the file exists, it is truncated to - zero size upon opening. - */ - append, - - /** Appending to an existing file. - - The current file position shall be set to the end of - the file prior to each write. - - If the file does not exist, an error is generated. - */ - append_existing -}; - -} // http_proto -} // boost - -#endif diff --git a/include/boost/http_proto/file_sink.hpp b/include/boost/http_proto/file_sink.hpp index 574e70fc..2cbc7fab 100644 --- a/include/boost/http_proto/file_sink.hpp +++ b/include/boost/http_proto/file_sink.hpp @@ -12,8 +12,8 @@ #define BOOST_HTTP_PROTO_FILE_SINK_HPP #include -#include #include +#include namespace boost { namespace http_proto { @@ -39,7 +39,7 @@ namespace http_proto { class file_sink : public sink { - file f_; + capy::file f_; public: /** Constructor. @@ -49,7 +49,7 @@ class file_sink */ BOOST_HTTP_PROTO_DECL explicit - file_sink(file&& f) noexcept; + file_sink(capy::file&& f) noexcept; file_sink() = delete; file_sink(file_sink const&) = delete; diff --git a/include/boost/http_proto/file_source.hpp b/include/boost/http_proto/file_source.hpp index 88d98a21..a7f71555 100644 --- a/include/boost/http_proto/file_source.hpp +++ b/include/boost/http_proto/file_source.hpp @@ -12,8 +12,8 @@ #define BOOST_HTTP_PROTO_FILE_SOURCE_HPP #include -#include #include +#include #include namespace boost { @@ -41,7 +41,7 @@ namespace http_proto { class file_source : public source { - file f_; + capy::file f_; std::uint64_t n_; public: @@ -57,7 +57,7 @@ class file_source */ BOOST_HTTP_PROTO_DECL file_source( - file&& f, + capy::file&& f, std::uint64_t limit = std::uint64_t(-1)) noexcept; diff --git a/include/boost/http_proto/json.hpp b/include/boost/http_proto/json.hpp new file mode 100644 index 00000000..50399c89 --- /dev/null +++ b/include/boost/http_proto/json.hpp @@ -0,0 +1,22 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_JSON_HPP +#define BOOST_HTTP_PROTO_JSON_HPP + +#include +#include + +namespace boost { +namespace http_proto { + +} // http_proto +} // boost + +#endif diff --git a/include/boost/http_proto/parser.hpp b/include/boost/http_proto/parser.hpp index 6af8c8dd..5441d236 100644 --- a/include/boost/http_proto/parser.hpp +++ b/include/boost/http_proto/parser.hpp @@ -287,6 +287,12 @@ class parser parse( system::error_code& ec); + /** Return true if a body has been attached. + */ + BOOST_HTTP_PROTO_DECL + bool + is_body_set() const noexcept; + /** Attach an elastic buffer body. This function attaches the specified elastic @@ -634,10 +640,6 @@ class parser detail::workspace& ws() noexcept; - BOOST_HTTP_PROTO_DECL - bool - is_body_set() const noexcept; - BOOST_HTTP_PROTO_DECL void set_body_impl(buffers::any_dynamic_buffer&) noexcept; diff --git a/include/boost/http_proto/server/helmet.hpp b/include/boost/http_proto/server/helmet.hpp new file mode 100644 index 00000000..8c728e6b --- /dev/null +++ b/include/boost/http_proto/server/helmet.hpp @@ -0,0 +1,365 @@ +// +// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_SERVER_HELMET_HPP +#define BOOST_HTTP_PROTO_SERVER_HELMET_HPP + +#include +#include + +namespace boost { +namespace http_proto { + +/** Configuration options for helmet security middleware. + + This structure holds a collection of HTTP security headers + that will be applied to responses. Headers are stored as + key-value pairs where each key can have multiple values. + + @see helmet +*/ +struct helmet_options +{ + using pair_type = std::pair>; + using map_type = std::vector; + + /** Collection of security headers to apply */ + map_type headers; + + /** Set or update a security header. + + If a header with the same name already exists, it will be replaced. + Otherwise, the new header is appended to the collection. + @param helmet_hdr The header name and values to set. + + @return A reference to this object for chaining. + */ + helmet_options& set(const pair_type& helmet_hdr); + + helmet_options(); + ~helmet_options(); +}; + +/** Download behavior for X-Download-Options header. + + Controls how browsers handle file downloads. +*/ +enum class helmet_download_type +{ + /** Prevent opening files directly in IE8+ */ + noopen, + + /** Disable the X-Download-Options header */ + disabled +}; + +/** Frame origin policy for X-Frame-Options header. + + Controls whether the page can be embedded in frames. +*/ +enum class helmet_origin_type +{ + /** Prevent all framing */ + deny, + + /** Allow framing from same origin only */ + sameorigin +}; + +/** Content Security Policy directive values. + + Defines standard CSP source expressions. + + @see helmet::csp_policy +*/ +enum class csp_type +{ + /** Refers to the current origin */ + self, + + /** Blocks all sources */ + none, + + /** Allows inline scripts/styles */ + unsafe_inline, + + /** Allows eval() and similar */ + unsafe_eval, + + /** Enables strict dynamic mode */ + strict_dynamic, + + /** Enables sandbox restrictions */ + sandbox, + + /** Includes samples in violation reports */ + report_sample +}; + +/** Cross-Origin-Opener-Policy values. + + Controls cross-origin window isolation. +*/ +enum class coop_policy_type +{ + /** No isolation (default browser behavior) */ + unsafe_none, + + /** Allow popups from same origin */ + same_origin_allow_popups, + + /** Strict same-origin isolation */ + same_origin +}; + +/** Cross-Origin-Resource-Policy values. + + Controls cross-origin resource sharing. +*/ +enum class corp_policy_type +{ + /** Only same-origin requests allowed */ + same_origin, + + /** Same-site requests allowed */ + same_site, + + /** Cross-origin requests allowed */ + cross_origin +}; + +/** Cross-Origin-Embedder-Policy values. + + Controls embedding of cross-origin resources. +*/ +enum class coep_policy_type +{ + /** No restrictions (default) */ + unsafe_none, + + /** Require CORP header on cross-origin resources */ + require_corp, + + /** Load cross-origin resources without credentials */ + credentialless +}; + +/** Referrer-Policy values. + + Controls how much referrer information is sent with requests. +*/ +enum class referrer_policy_type +{ + /** Never send referrer */ + no_referrer, + + /** Send referrer for HTTPS→HTTPS, not HTTPS→HTTP */ + no_referrer_when_downgrade, + + /** Send referrer for same-origin requests only */ + same_origin, + + /** Send origin only */ + origin, + + /** Send origin only for HTTPS→HTTPS */ + strict_origin, + + /** Send full URL for same-origin, origin for cross-origin */ + origin_when_cross_origin, + + /** Send origin only for HTTPS→HTTPS, nothing for HTTPS→HTTP */ + strict_origin_when_cross_origin, + + /** Always send full URL (unsafe) */ + unsafe_url +}; + +/** X-Permitted-Cross-Domain-Policies values. + + Controls cross-domain policy files (Flash, Acrobat). +*/ +enum class cross_domain_policy_type +{ + /** No policy files allowed */ + none, + + /** Only master policy file */ + master_only, + + /** Policy files with matching content-type */ + by_content_type, + + /** Policy files via FTP filename */ + by_ftp_filename, + + /** All policy files allowed */ + all +}; + +/** HSTS constants namespace. */ +namespace hsts { + inline static constexpr bool preload = true; + inline static constexpr bool no_preload = false; + inline static constexpr bool include_subdomains = true; + inline static constexpr bool no_subdomains = false; + inline static constexpr size_t default_age = 31536000; +} + +/** List of allowed sources for CSP directives */ +using csp_allow_list = std::vector; + +/** Header name and values pair */ +using option_pair = std::pair>; + +/** Security middleware inspired by helmet.js. + + Helmet helps secure HTTP responses by setting various + HTTP security headers. It provides protection against + common web vulnerabilities like XSS, clickjacking, + and other attacks. + + @par Example + @code + helmet_options opts; + opts.set(x_frame_origin(helmet_origin_type::deny)); + + helmet::csp_policy csp; + csp.allow("script-src", csp_type::self); + opts.set(content_security_policy(csp)); + + srv.wwwroot.use(helmet(opts)); + @endcode + + @see helmet_options +*/ +class helmet +{ + struct impl; + impl* impl_{}; + +public: + BOOST_HTTP_PROTO_DECL + explicit helmet( + helmet_options options = {}); + + BOOST_HTTP_PROTO_DECL + ~helmet(); + + helmet& operator=(helmet&&) noexcept; + helmet(helmet&&) noexcept; + + BOOST_HTTP_PROTO_DECL + route_result + operator()(route_params& p) const; + + /** Content Security Policy builder. + + Provides a fluent interface for constructing CSP headers + with multiple directives and sources. + + @par Example + @code + helmet::csp_policy policy; + policy.allow("script-src", csp_type::self) + .allow("style-src", "https://example.com") + .allow("img-src", csp_type::none); + @endcode + */ + struct csp_policy + { + /** List of CSP directives */ + csp_allow_list list; + + BOOST_HTTP_PROTO_DECL + csp_policy(); + + BOOST_HTTP_PROTO_DECL + ~csp_policy(); + + /** Allows a CSP directive with a predefined type. + + @param allow The directive name (e.g., "script-src"). + @param type The CSP source expression type. + + @return A reference to this object for chaining. + */ + BOOST_HTTP_PROTO_DECL + csp_policy& allow(const core::string_view& allow, + const csp_type& type); + + /** Remove a CSP directive. + + @param allow The directive name to remove. + + @return A reference to this object for chaining. + + @throws std::invalid_argument if the directive is not found. + */ + BOOST_HTTP_PROTO_DECL + csp_policy& remove(const core::string_view& allow); + + /** Append a CSP directive with a URL source. + + @param allow The directive name (e.g., "script-src"). + @param source The URL to allow. + + @return A reference to this object for chaining. + + @throws std::invalid_argument if parameters are empty. + */ + BOOST_HTTP_PROTO_DECL + csp_policy& allow(const core::string_view& allow, + const urls::url_view& source); + }; +}; + +option_pair x_download_options(const helmet_download_type& type); + +option_pair x_frame_origin(const helmet_origin_type& origin); + +option_pair x_xss_protection(); + +option_pair x_content_type_options(); + +option_pair content_security_policy(const helmet::csp_policy& sp); + +/** Return HSTS configuration for the host. + @param age Maximum age in seconds for HSTS enforcement. + @param include_subdomains either hsts::include_subdomains or hsts::no_subdomains + @param preload either hsts::preload or hsts::no_preload + @note Use constants from the hsts namespace. + @return Header name and value pair. +*/ +option_pair strict_transport_security( + std::size_t age, + bool include_subdomains = hsts::include_subdomains, + bool preload = hsts::no_preload); + +option_pair cross_origin_opener_policy(const coop_policy_type& policy = coop_policy_type::same_origin); + +option_pair cross_origin_resource_policy(const corp_policy_type& policy = corp_policy_type::same_origin); + +option_pair cross_origin_embedder_policy(const coep_policy_type& policy = coep_policy_type::require_corp); + +option_pair referrer_policy(const referrer_policy_type& policy = referrer_policy_type::no_referrer); + +option_pair origin_agent_cluster(); + +option_pair dns_prefetch_control(bool allow = false); + +option_pair permitted_cross_domain_policies(const cross_domain_policy_type& policy = cross_domain_policy_type::none); + +option_pair hide_powered_by(); + +} + +} + +#endif diff --git a/include/boost/http_proto/server/route_handler.hpp b/include/boost/http_proto/server/route_handler.hpp index 23d07254..391813b9 100644 --- a/include/boost/http_proto/server/route_handler.hpp +++ b/include/boost/http_proto/server/route_handler.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include // VFALCO forward declare? #include // VFALCO forward declare? @@ -81,6 +82,10 @@ struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE */ suspender suspend; + /** Executor associated with the session. + */ + capy::executor ex; + /** Destructor */ BOOST_HTTP_PROTO_DECL @@ -110,6 +115,45 @@ struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE route_params& set_body(std::string s); + /** Read the request body and receive a value. + + This function reads the entire request body into the specified sink. + When the read operation completes, the given callback is invoked with + the result. + + The @p callback parameter must be a function object with this + equivalent signature, where `T` is the type produced by the value sink: + @code + void ( T&& t ); + @endcode + + @par Example + @code + rp.read_body( + capy::string_body_sink(), + []( std::string s ) + { + // body read successfully + }); + @endcode + + If an error or an exception occurs during the read, it is propagated + through the router to the next error or exception handler. + + @param sink The body sink to read into. + @param callback The function to call when the read completes. + @return The route result, which must be returned immediately + from the route handler. + */ + template< + class ValueSink, + class Callback> + auto + read_body( + ValueSink&& sink, + Callback&& callback) -> + route_result; + #ifdef BOOST_HTTP_PROTO_HAS_CORO /** Spawn a coroutine for this route. @@ -137,145 +181,60 @@ struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE from the route handler. */ BOOST_HTTP_PROTO_DECL - virtual auto spawn( + auto spawn( capy::task coro) -> route_result; #endif - // VFALCO this doc isn't quite right because it doesn't explain - // the possibility that post will return the final result immediately, - // and it needs to remind the user that calling a function which - // returns route_result means they have to return the value right away - // without doing anything else. - // - // VFALCO we have to detect calls to suspend inside `f` and throw - // - /** Submit cooperative work. - - This function suspend the current handler from the session, - and immediately invokes the specified function object @p f. - When the function returns normally, the function object is - placed into an implementation-defined work queue to be invoked - again. Otherwise, if the function calls `resume(rv)` then the - session is resumed and behaves as if the original route handler - had returned the value `rv`. - - When the function object is invoked, it runs in the same context - as the original handler invocation. If the function object - attempts to call @ref post again, or attempts to call @ref suspend - an exception is thrown. - - The function object @p f must have this equivalent signature: - @code - void ( resumer resume ); - @endcode - - @param f The function object to invoke. - @param c The continuation function to be invoked when f finishes. - */ - template - auto - post(F&& f) -> route_result; - protected: - /** A task to be invoked later - */ - struct task - { - virtual ~task() = default; - - /** Invoke the task. - - @return true if the task resumed the session. - */ - virtual bool invoke() = 0; - }; - - /** Post task_ to be invoked later - - Subclasses must schedule task_ to be invoked at an unspecified - point in the future. - */ - BOOST_HTTP_PROTO_DECL - virtual void do_post(); - - std::unique_ptr task_; + std::function finish_; }; //----------------------------------------------- -template +template< + class ValueSink, + class Callback> auto route_params:: -post(F&& f) -> route_result +read_body( + ValueSink&& sink, + Callback&& callback) -> + route_result { - // task already posted - if(task_) - detail::throw_invalid_argument(); - - struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE - immediate : suspender::owner - { - route_result rv; - bool set = false; - void do_resume( - route_result const& rv_) override - { - rv = rv_; - set = true; - } - }; + using T = typename std::decay::type; - class BOOST_HTTP_PROTO_SYMBOL_VISIBLE model - : public task - , public suspender::owner + struct on_finish { - public: - model(route_params& p, - F&& f, resumer resume) - : p_(p) - , f_(std::forward(f)) - , resume_(resume) + T& sink; + resumer resume; + typename std::decay::type cb; + + on_finish( + T& sink_, + resumer resume_, + Callback&& cb_) + : sink(sink_) + , resume(resume_) + , cb(std::forward(cb_)) { } - bool invoke() override + void operator()() { - resumed_ = false; - // VFALCO analyze exception safety - f_(resumer(*this)); - return resumed_; + resume(std::move(cb)(sink.release())); } - - void do_resume( - route_result const& rv) override - { - resumed_ = true; - resumer resume(resume_); - p_.task_.reset(); // destroys *this - resume(rv); - } - - private: - route_params& p_; - typename std::decay::type f_; - resumer resume_; - bool resumed_; }; - // first call - immediate impl; - f(resumer(impl)); - if(impl.set) - return impl.rv; - return suspend( [&](resumer resume) { - task_ = std::unique_ptr(new model( - *this, std::forward(f), resume)); - do_post(); + finish_ = on_finish( + this->parser.set_body( + std::forward(sink)), + resume, + std::forward(callback)); }); } diff --git a/include/boost/http_proto/server/router_types.hpp b/include/boost/http_proto/server/router_types.hpp index b52cb647..b9948750 100644 --- a/include/boost/http_proto/server/router_types.hpp +++ b/include/boost/http_proto/server/router_types.hpp @@ -167,8 +167,10 @@ class suspender struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE owner { - BOOST_HTTP_PROTO_DECL virtual resumer do_suspend(); + BOOST_HTTP_PROTO_DECL + virtual resumer do_suspend(); virtual void do_resume(route_result const&) = 0; + virtual void do_resume(std::exception_ptr) = 0; }; suspender() = default; @@ -253,7 +255,7 @@ class resumer When a session is resumed, routing continues as if the handler had returned the @ref route_result contained in @p rv. - @param ec The error code to resume with. + @param rv The route result to resume with. @throw std::invalid_argument If the object is empty. */ @@ -265,6 +267,23 @@ class resumer p_->do_resume(rv); } + /** Resume the session with an exception + + When a session is resumed with an exception, the exception + is propagated through the router's error handling mechanism. + + @param ep The exception to propagate. + + @throw std::invalid_argument If the object is empty. + */ + void operator()( + std::exception_ptr ep) const + { + if(! p_) + detail::throw_invalid_argument(); + p_->do_resume(ep); + } + private: suspender::owner* p_ #if defined(__clang__) diff --git a/src/detail/file_posix.cpp b/src/detail/file_posix.cpp deleted file mode 100644 index cc1f14f5..00000000 --- a/src/detail/file_posix.cpp +++ /dev/null @@ -1,358 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#include - -#if BOOST_HTTP_PROTO_USE_POSIX_FILE - -#include -#include -#include -#include -#include -#include -#include -#include - -#if ! defined(BOOST_HTTP_PROTO_NO_POSIX_FADVISE) -# if defined(__APPLE__) || (defined(__ANDROID__) && (__ANDROID_API__ < 21)) -# define BOOST_HTTP_PROTO_NO_POSIX_FADVISE -# endif -#endif - -#if ! defined(BOOST_HTTP_PROTO_USE_POSIX_FADVISE) -# if ! defined(BOOST_HTTP_PROTO_NO_POSIX_FADVISE) -# define BOOST_HTTP_PROTO_USE_POSIX_FADVISE 1 -# else -# define BOOST_HTTP_PROTO_USE_POSIX_FADVISE 0 -# endif -#endif - -namespace boost { -namespace http_proto { -namespace detail { - -int -file_posix:: -native_close(native_handle_type& fd) -{ -/* https://github.com/boostorg/beast/issues/1445 - - This function is tuned for Linux / Mac OS: - - * only calls close() once - * returns the error directly to the caller - * does not loop on EINTR - - If this is incorrect for the platform, then the - caller will need to implement their own type - meeting the File requirements and use the correct - behavior. - - See: - http://man7.org/linux/man-pages/man2/close.2.html -*/ - int ev = 0; - if(fd != -1) - { - if(::close(fd) != 0) - ev = errno; - fd = -1; - } - return ev; -} - -file_posix:: -~file_posix() -{ - native_close(fd_); -} - -file_posix:: -file_posix( - file_posix&& other) noexcept - : fd_(boost::exchange(other.fd_, -1)) -{ -} - -file_posix& -file_posix:: -operator=( - file_posix&& other) noexcept -{ - if(&other == this) - return *this; - native_close(fd_); - fd_ = other.fd_; - other.fd_ = -1; - return *this; -} - -void -file_posix:: -native_handle(native_handle_type fd) -{ - native_close(fd_); - fd_ = fd; -} - -void -file_posix:: -close( - system::error_code& ec) -{ - auto const ev = native_close(fd_); - if(ev) - ec.assign(ev, - system::system_category()); - else - ec = {}; -} - -void -file_posix:: -open(char const* path, file_mode mode, system::error_code& ec) -{ - auto const ev = native_close(fd_); - if(ev) - ec.assign(ev, - system::system_category()); - else - ec = {}; - - int f = 0; -#if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - int advise = 0; -#endif - switch(mode) - { - default: - case file_mode::read: - f = O_RDONLY; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_RANDOM; - #endif - break; - case file_mode::scan: - f = O_RDONLY; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_SEQUENTIAL; - #endif - break; - - case file_mode::write: - f = O_RDWR | O_CREAT | O_TRUNC; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_RANDOM; - #endif - break; - - case file_mode::write_new: - f = O_RDWR | O_CREAT | O_EXCL; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_RANDOM; - #endif - break; - - case file_mode::write_existing: - f = O_RDWR | O_EXCL; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_RANDOM; - #endif - break; - - case file_mode::append: - f = O_WRONLY | O_CREAT | O_APPEND; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_SEQUENTIAL; - #endif - break; - - case file_mode::append_existing: - f = O_WRONLY | O_APPEND; - #if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - advise = POSIX_FADV_SEQUENTIAL; - #endif - break; - } - for(;;) - { - fd_ = ::open(path, f, 0644); - if(fd_ != -1) - break; - auto const ev = errno; - if(ev != EINTR) - { - ec.assign(ev, - system::system_category()); - return; - } - } -#if BOOST_HTTP_PROTO_USE_POSIX_FADVISE - if(::posix_fadvise(fd_, 0, 0, advise)) - { - auto const ev = errno; - native_close(fd_); - ec.assign(ev, - system::system_category()); - return; - } -#endif - ec = {}; -} - -std::uint64_t -file_posix:: -size( - system::error_code& ec) const -{ - if(fd_ == -1) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - struct stat st; - if(::fstat(fd_, &st) != 0) - { - ec.assign(errno, - system::system_category()); - return 0; - } - ec = {}; - return st.st_size; -} - -std::uint64_t -file_posix:: -pos( - system::error_code& ec) const -{ - if(fd_ == -1) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - auto const result = ::lseek(fd_, 0, SEEK_CUR); - if(result == (::off_t)-1) - { - ec.assign(errno, - system::system_category()); - return 0; - } - ec = {}; - return result; -} - -void -file_posix:: -seek(std::uint64_t offset, - system::error_code& ec) -{ - if(fd_ == -1) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return; - } - auto const result = ::lseek(fd_, offset, SEEK_SET); - if(result == static_cast<::off_t>(-1)) - { - ec.assign(errno, - system::system_category()); - return; - } - ec = {}; -} - -std::size_t -file_posix:: -read(void* buffer, std::size_t n, - system::error_code& ec) -{ - if(fd_ == -1) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - std::size_t nread = 0; - while(n > 0) - { - // not required to define SSIZE_MAX so we avoid it - constexpr auto ssmax = - static_cast((std::numeric_limits< - decltype(::read(fd_, buffer, n))>::max)()); - auto const amount = (std::min)( - n, ssmax); - auto const result = ::read(fd_, buffer, amount); - if(result == -1) - { - auto const ev = errno; - if(ev == EINTR) - continue; - ec.assign(ev, - system::system_category()); - return nread; - } - if(result == 0) - { - // short read - return nread; - } - n -= result; - nread += result; - buffer = static_cast(buffer) + result; - } - return nread; -} - -std::size_t -file_posix:: -write(void const* buffer, std::size_t n, - system::error_code& ec) -{ - if(fd_ == -1) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - std::size_t nwritten = 0; - while(n > 0) - { - // not required to define SSIZE_MAX so we avoid it - constexpr auto ssmax = - static_cast((std::numeric_limits< - decltype(::write(fd_, buffer, n))>::max)()); - auto const amount = (std::min)( - n, ssmax); - auto const result = ::write(fd_, buffer, amount); - if(result == -1) - { - auto const ev = errno; - if(ev == EINTR) - continue; - ec.assign(ev, - system::system_category()); - return nwritten; - } - n -= result; - nwritten += result; - buffer = static_cast(buffer) + result; - } - return nwritten; -} - -} // detail -} // http_proto -} // boost - -#endif diff --git a/src/detail/file_stdio.cpp b/src/detail/file_stdio.cpp deleted file mode 100644 index a2fa3717..00000000 --- a/src/detail/file_stdio.cpp +++ /dev/null @@ -1,357 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#include "src/detail/win32_unicode_path.hpp" -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -file_stdio:: -~file_stdio() -{ - if(f_) - fclose(f_); -} - -file_stdio:: -file_stdio( - file_stdio&& other) noexcept - : f_(boost::exchange(other.f_, nullptr)) -{ -} - -file_stdio& -file_stdio:: -operator=( - file_stdio&& other) noexcept -{ - if(&other == this) - return *this; - if(f_) - fclose(f_); - f_ = other.f_; - other.f_ = nullptr; - return *this; -} - -void -file_stdio:: -native_handle(std::FILE* f) -{ - if(f_) - fclose(f_); - f_ = f; -} - -void -file_stdio:: -close( - system::error_code& ec) -{ - if(f_) - { - int failed = fclose(f_); - f_ = nullptr; - if(failed) - { - ec.assign(errno, - system::generic_category()); - return; - } - } - ec = {}; -} - -void -file_stdio:: -open(char const* path, file_mode mode, - system::error_code& ec) -{ - if(f_) - { - fclose(f_); - f_ = nullptr; - } - ec = {}; -#ifdef _WIN32 - boost::winapi::WCHAR_ const* s; - detail::win32_unicode_path unicode_path(path, ec); - if (ec) - return; -#else - char const* s; -#endif - switch(mode) - { - default: - case file_mode::read: - #ifdef _WIN32 - s = L"rb"; - #else - s = "rb"; - #endif - break; - - case file_mode::scan: - #ifdef _WIN32 - s = L"rbS"; - #else - s = "rb"; - #endif - break; - - case file_mode::write: - #ifdef _WIN32 - s = L"wb+"; - #else - s = "wb+"; - #endif - break; - - case file_mode::write_new: - { -#ifdef _WIN32 -# if (defined(BOOST_MSVC) && BOOST_MSVC >= 1910) || (defined(_MSVC_STL_VERSION) && _MSVC_STL_VERSION >= 141) - s = L"wbx"; -# else - std::FILE* f0; - auto const ev = ::_wfopen_s(&f0, unicode_path.c_str(), L"rb"); - if(! ev) - { - std::fclose(f0); - ec = make_error_code( - system::errc::file_exists); - return; - } - else if(ev != - system::errc::no_such_file_or_directory) - { - ec.assign(ev, - system::generic_category()); - return; - } - s = L"wb"; -# endif -#else - s = "wbx"; -#endif - break; - } - - case file_mode::write_existing: - #ifdef _WIN32 - s = L"rb+"; - #else - s = "rb+"; - #endif - break; - - case file_mode::append: - #ifdef _WIN32 - s = L"ab"; - #else - s = "ab"; - #endif - break; - - case file_mode::append_existing: - { -#ifdef _WIN32 - std::FILE* f0; - auto const ev = - ::_wfopen_s(&f0, unicode_path.c_str(), L"rb+"); - if(ev) - { - ec.assign(ev, - system::generic_category()); - return; - } -#else - auto const f0 = - std::fopen(path, "rb+"); - if(! f0) - { - ec.assign(errno, - system::generic_category()); - return; - } -#endif - std::fclose(f0); - #ifdef _WIN32 - s = L"ab"; - #else - s = "ab"; - #endif - break; - } - } - -#ifdef _WIN32 - auto const ev = ::_wfopen_s( - &f_, unicode_path.c_str(), s); - if(ev) - { - f_ = nullptr; - ec.assign(ev, - system::generic_category()); - return; - } -#else - f_ = std::fopen(path, s); - if(! f_) - { - ec.assign(errno, - system::generic_category()); - return; - } -#endif -} - -std::uint64_t -file_stdio:: -size( - system::error_code& ec) const -{ - if(! f_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - long pos = std::ftell(f_); - if(pos == -1L) - { - ec.assign(errno, - system::generic_category()); - return 0; - } - int result = std::fseek(f_, 0, SEEK_END); - if(result != 0) - { - ec.assign(errno, - system::generic_category()); - return 0; - } - long size = std::ftell(f_); - if(size == -1L) - { - ec.assign(errno, - system::generic_category()); - std::fseek(f_, pos, SEEK_SET); - return 0; - } - result = std::fseek(f_, pos, SEEK_SET); - if(result != 0) - ec.assign(errno, - system::generic_category()); - else - ec = {}; - return size; -} - -std::uint64_t -file_stdio:: -pos( - system::error_code& ec) const -{ - if(! f_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - long pos = std::ftell(f_); - if(pos == -1L) - { - ec.assign(errno, - system::generic_category()); - return 0; - } - ec = {}; - return pos; -} - -void -file_stdio:: -seek(std::uint64_t offset, - system::error_code& ec) -{ - if(! f_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return; - } - if(offset > static_cast((std::numeric_limits::max)())) - { - ec = make_error_code( - system::errc::invalid_seek); - return; - } - int result = std::fseek(f_, - static_cast(offset), SEEK_SET); - if(result != 0) - ec.assign(errno, - system::generic_category()); - else - ec = {}; -} - -std::size_t -file_stdio:: -read(void* buffer, std::size_t n, - system::error_code& ec) -{ - if(! f_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - auto nread = std::fread(buffer, 1, n, f_); - if(std::ferror(f_)) - { - ec.assign(errno, - system::generic_category()); - return 0; - } - return nread; -} - -std::size_t -file_stdio:: -write(void const* buffer, std::size_t n, - system::error_code& ec) -{ - if(! f_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - auto nwritten = std::fwrite(buffer, 1, n, f_); - if(std::ferror(f_)) - { - ec.assign(errno, - system::generic_category()); - return 0; - } - return nwritten; -} - -} // detail -} // http_proto -} // boost diff --git a/src/detail/file_win32.cpp b/src/detail/file_win32.cpp deleted file mode 100644 index e5203fc1..00000000 --- a/src/detail/file_win32.cpp +++ /dev/null @@ -1,385 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#include - -#if BOOST_HTTP_PROTO_USE_WIN32_FILE - -#include "src/detail/win32_unicode_path.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -// VFALCO Can't seem to get boost/detail/winapi to work with -// this so use the non-Ex version for now. -BOOST_HTTP_PROTO_DECL -winapi::BOOL_ -set_file_pointer_ex( - winapi::HANDLE_ hFile, - winapi::LARGE_INTEGER_ lpDistanceToMove, - winapi::PLARGE_INTEGER_ lpNewFilePointer, - winapi::DWORD_ dwMoveMethod) -{ - auto dwHighPart = lpDistanceToMove.u.HighPart; - auto dwLowPart = winapi::SetFilePointer( - hFile, - lpDistanceToMove.u.LowPart, - &dwHighPart, - dwMoveMethod); - if(dwLowPart == winapi::INVALID_SET_FILE_POINTER_) - return 0; - if(lpNewFilePointer) - { - lpNewFilePointer->u.LowPart = dwLowPart; - lpNewFilePointer->u.HighPart = dwHighPart; - } - return 1; -} - -file_win32:: -~file_win32() -{ - if(h_ != winapi::INVALID_HANDLE_VALUE_) - winapi::CloseHandle(h_); -} - -file_win32:: -file_win32( - file_win32&& other) noexcept - : h_(boost::exchange(other.h_, - winapi::INVALID_HANDLE_VALUE_)) -{ -} - -file_win32& -file_win32:: -operator=( - file_win32&& other) noexcept -{ - if(&other == this) - return *this; - if(h_) - winapi::CloseHandle(h_); - h_ = other.h_; - other.h_ = winapi::INVALID_HANDLE_VALUE_; - return *this; -} - -void -file_win32:: -native_handle(native_handle_type h) -{ - if(h_ != winapi::INVALID_HANDLE_VALUE_) - winapi::CloseHandle(h_); - h_ = h; -} - -void -file_win32:: -close( - system::error_code& ec) -{ - if(h_ != winapi::INVALID_HANDLE_VALUE_) - { - if(! winapi::CloseHandle(h_)) - ec.assign( - winapi::GetLastError(), - system::system_category()); - else - ec = {}; - h_ = winapi::INVALID_HANDLE_VALUE_; - } - else - { - ec = {}; - } -} - -void -file_win32:: -open(char const* path, file_mode mode, - system::error_code& ec) -{ - if(h_ != winapi::INVALID_HANDLE_VALUE_) - { - winapi::CloseHandle(h_); - h_ = winapi::INVALID_HANDLE_VALUE_; - } - winapi::DWORD_ share_mode = 0; - winapi::DWORD_ desired_access = 0; - winapi::DWORD_ creation_disposition = 0; - winapi::DWORD_ flags_and_attributes = 0; -/* - | When the file... - This argument: | Exists Does not exist - -------------------------+------------------------------------------------------ - CREATE_ALWAYS | Truncates Creates - CREATE_NEW +-----------+ Fails Creates - OPEN_ALWAYS ===| does this |===> Opens Creates - OPEN_EXISTING +-----------+ Opens Fails - TRUNCATE_EXISTING | Truncates Fails -*/ - switch(mode) - { - default: - case file_mode::read: - desired_access = winapi::GENERIC_READ_; - share_mode = winapi::FILE_SHARE_READ_; - creation_disposition = winapi::OPEN_EXISTING_; - flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS - break; - - case file_mode::scan: - desired_access = winapi::GENERIC_READ_; - share_mode = winapi::FILE_SHARE_READ_; - creation_disposition = winapi::OPEN_EXISTING_; - flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN - break; - - case file_mode::write: - desired_access = winapi::GENERIC_READ_ | - winapi::GENERIC_WRITE_; - creation_disposition = winapi::CREATE_ALWAYS_; - flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS - break; - - case file_mode::write_new: - desired_access = winapi::GENERIC_READ_ | - winapi::GENERIC_WRITE_; - creation_disposition = winapi::CREATE_NEW_; - flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS - break; - - case file_mode::write_existing: - desired_access = winapi::GENERIC_READ_ | - winapi::GENERIC_WRITE_; - creation_disposition = winapi::OPEN_EXISTING_; - flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS - break; - - case file_mode::append: - desired_access = winapi::GENERIC_READ_ | - winapi::GENERIC_WRITE_; - - creation_disposition = winapi::OPEN_ALWAYS_; - flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN - break; - - case file_mode::append_existing: - desired_access = winapi::GENERIC_READ_ | - winapi::GENERIC_WRITE_; - creation_disposition = winapi::OPEN_EXISTING_; - flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN - break; - } - - detail::win32_unicode_path unicode_path(path, ec); - if (ec) - return; - h_ = ::CreateFileW( - unicode_path.c_str(), - desired_access, - share_mode, - NULL, - creation_disposition, - flags_and_attributes, - NULL); - if (h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec.assign(winapi::GetLastError(), - system::system_category()); - return; - } - if (mode == file_mode::append || - mode == file_mode::append_existing) - { - winapi::LARGE_INTEGER_ in; - in.QuadPart = 0; - if (!detail::set_file_pointer_ex(h_, in, 0, - winapi::FILE_END_)) - { - ec.assign(winapi::GetLastError(), - system::system_category()); - winapi::CloseHandle(h_); - h_ = winapi::INVALID_HANDLE_VALUE_; - return; - } - } - ec = {}; -} - -std::uint64_t -file_win32:: -size( - system::error_code& ec) const -{ - if(h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - winapi::LARGE_INTEGER_ fileSize; - if(! winapi::GetFileSizeEx(h_, &fileSize)) - { - ec.assign(winapi::GetLastError(), - system::system_category()); - return 0; - } - ec = {}; - return fileSize.QuadPart; -} - -std::uint64_t -file_win32:: -pos( - system::error_code& ec) const -{ - if(h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - winapi::LARGE_INTEGER_ in; - winapi::LARGE_INTEGER_ out; - in.QuadPart = 0; - if(! detail::set_file_pointer_ex(h_, in, &out, - winapi::FILE_CURRENT_)) - { - ec.assign(winapi::GetLastError(), - system::system_category()); - return 0; - } - ec = {}; - return out.QuadPart; -} - -void -file_win32:: -seek(std::uint64_t offset, - system::error_code& ec) -{ - if(h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return; - } - winapi::LARGE_INTEGER_ in; - in.QuadPart = offset; - if(! detail::set_file_pointer_ex(h_, in, 0, - winapi::FILE_BEGIN_)) - { - ec.assign(winapi::GetLastError(), - system::system_category()); - return; - } - ec = {}; -} - -std::size_t -file_win32:: -read(void* buffer, std::size_t n, - system::error_code& ec) -{ - if(h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - std::size_t nread = 0; - while(n > 0) - { - winapi::DWORD_ amount; - if(n > (std::numeric_limits< - winapi::DWORD_>::max)()) - amount = (std::numeric_limits< - winapi::DWORD_>::max)(); - else - amount = static_cast< - winapi::DWORD_>(n); - winapi::DWORD_ bytesRead; - if(! ::ReadFile(h_, buffer, amount, &bytesRead, 0)) - { - auto const dwError = winapi::GetLastError(); - if(dwError != winapi::ERROR_HANDLE_EOF_) - ec.assign(dwError, - system::system_category()); - else - ec = {}; - return nread; - } - if(bytesRead == 0) - return nread; - n -= bytesRead; - nread += bytesRead; - buffer = static_cast(buffer) + bytesRead; - } - ec = {}; - return nread; -} - -std::size_t -file_win32:: -write(void const* buffer, std::size_t n, - system::error_code& ec) -{ - if(h_ == winapi::INVALID_HANDLE_VALUE_) - { - ec = make_error_code( - system::errc::bad_file_descriptor); - return 0; - } - std::size_t nwritten = 0; - while(n > 0) - { - winapi::DWORD_ amount; - if(n > (std::numeric_limits< - winapi::DWORD_>::max)()) - amount = (std::numeric_limits< - winapi::DWORD_>::max)(); - else - amount = static_cast< - winapi::DWORD_>(n); - winapi::DWORD_ bytesWritten; - if(! ::WriteFile(h_, buffer, amount, &bytesWritten, 0)) - { - auto const dwError = winapi::GetLastError(); - if(dwError != winapi::ERROR_HANDLE_EOF_) - ec.assign(dwError, - system::system_category()); - else - ec = {}; - return nwritten; - } - if(bytesWritten == 0) - return nwritten; - n -= bytesWritten; - nwritten += bytesWritten; - buffer = static_cast(buffer) + bytesWritten; - } - ec = {}; - return nwritten; -} - -} // detail -} // http_proto -} // boost - -#endif diff --git a/src/file_sink.cpp b/src/file_sink.cpp index b6df9c2d..3f4bf723 100644 --- a/src/file_sink.cpp +++ b/src/file_sink.cpp @@ -14,7 +14,8 @@ namespace boost { namespace http_proto { file_sink:: -file_sink(file&& f) noexcept +file_sink( + capy::file&& f) noexcept : f_(std::move(f)) { } diff --git a/src/file_source.cpp b/src/file_source.cpp index 7cded95b..df9a11e1 100644 --- a/src/file_source.cpp +++ b/src/file_source.cpp @@ -21,7 +21,7 @@ file_source(file_source&&) noexcept = default; file_source:: file_source( - file&& f, + capy::file&& f, std::uint64_t limit) noexcept : f_(std::move(f)) , n_(limit) diff --git a/src/server/helmet.cpp b/src/server/helmet.cpp new file mode 100644 index 00000000..5eb2b109 --- /dev/null +++ b/src/server/helmet.cpp @@ -0,0 +1,491 @@ +// +// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#include +#include +#include +#include + +namespace boost { +namespace http_proto { + +namespace detail { + + auto setHeaderValues(const std::vector& fields_value) -> std::string + { + if (fields_value.empty()) + return {}; + + std::string cached_fields; + + for (const auto& field : fields_value) + { + cached_fields += field; + cached_fields += "; "; + } + + return cached_fields; + } + +} + +helmet_options::helmet_options() +{ + // Default headers as per helmet.js defaults + this->set(x_download_options(helmet_download_type::noopen)); + this->set(x_frame_origin(helmet_origin_type::deny)); + this->set(x_xss_protection()); + this->set(x_content_type_options()); + this->set(strict_transport_security(hsts::default_age, hsts::include_subdomains, hsts::preload)); + this->set(cross_origin_opener_policy()); + this->set(cross_origin_resource_policy()); + this->set(origin_agent_cluster()); + this->set(referrer_policy()); + this->set(dns_prefetch_control()); + this->set(permitted_cross_domain_policies()); + this->set(hide_powered_by()); +} + +helmet_options::~helmet_options() = default; + +helmet_options& helmet_options::set(const pair_type& helmet_hdr) +{ + auto it_hdr = std::find_if(headers.begin(), headers.end(), [&helmet_hdr](const pair_type& pair) { + return pair.first == helmet_hdr.first; + }); + + if (it_hdr != headers.end()) + { + *it_hdr = helmet_hdr; + + return *this; + } + + headers.emplace_back(helmet_hdr); + return *this; +} + +/** + @brief private implementation details for the `helmet` middleware. +*/ +struct helmet::impl +{ + using pair_type = std::pair; + using vector_type = std::vector; + + helmet_options options_; + vector_type cached_headers_{}; +}; + +helmet:: +helmet( + helmet_options helmet_options) + : impl_(new impl()) +{ + impl_->options_ = std::move(helmet_options); + + std::for_each(impl_->options_.headers.begin(), impl_->options_.headers.end(), + [this] (const helmet_options::pair_type& hdr) + { + this->impl_->cached_headers_.emplace_back(hdr.first, + detail::setHeaderValues(hdr.second)); + }); +} + +helmet::~helmet() +{ + if (impl_ != nullptr) + delete impl_; + + impl_ = nullptr; +} + +helmet& helmet::operator=(helmet&& other) noexcept +{ + delete impl_; + impl_ = boost::exchange(other.impl_, nullptr); + + return *this; +} + +helmet:: +helmet(helmet&& other) noexcept +{ + delete impl_; + impl_ = boost::exchange(other.impl_, nullptr); +} + +route_result +helmet:: +operator()( + route_params& p) const +{ + std::for_each(impl_->cached_headers_.begin(), impl_->cached_headers_.end(), + [&p] (const impl::pair_type& hdr) + { + // If value is empty, it means we should remove the header (e.g., X-Powered-By) + if (hdr.second.empty()) + { + p.res.erase(hdr.first); + } + else + { + p.res.set(hdr.first, hdr.second); + } + }); + + return route::next; +} + +helmet::csp_policy& helmet::csp_policy::allow(const core::string_view& allow, + const urls::url_view& source) +{ + if (allow.empty()) + detail::throw_invalid_argument(); + + static std::vector const good_schemes{ + "http", + "https", + "wss", + "ws", + "file", + }; + + bool found_scheme{}; + + for (const auto& scheme : good_schemes) + { + found_scheme = source.scheme() == scheme; + } + if (!found_scheme) + detail::throw_invalid_argument(); + + std::string value = allow; + + value += " "; + value += source.data(); + + auto it = std::find_if(list.begin(), list.end(), [&allow](const std::string& in) { + std::string allow_with_space = std::string(allow) + " "; + return in.find(allow_with_space) != std::string::npos; + }); + + if (it == list.end()) + { + list.emplace_back(value); + } + else + { + auto& it_elem = *it; + + it_elem += " "; + it_elem += source.data(); + } + + return *this; +} + +helmet::csp_policy& helmet::csp_policy::allow(const core::string_view& allow, const csp_type& type) +{ + if (allow.empty()) + detail::throw_invalid_argument(); + + std::string value; + + switch (type) + { + case csp_type::sandbox: + { + value += " 'sandbox'"; + break; + } + case csp_type::none: + { + value += " 'none'"; + break; + } + case csp_type::unsafe_inline: + { + value += " 'unsafe-inline'"; + break; + } + case csp_type::unsafe_eval: + { + value += " 'unsafe-eval'"; + break; + } + case csp_type::strict_dynamic: + { + value += " 'strict-dynamic'"; + break; + } + case csp_type::report_sample: + { + value += " 'report-sample'"; + break; + } + case csp_type::self: + { + value += " 'self'"; + break; + } + default: + detail::throw_invalid_argument(); + return *this; + } + + auto it = std::find_if(list.begin(), list.end(), [&allow](const std::string& in) { + std::string allow_with_space = std::string(allow) + " "; + return in.find(allow_with_space) != std::string::npos; + }); + + if (it == list.end()) + { + std::string final_result = allow; + final_result += value; + + list.emplace_back(final_result); + } + else + { + auto& it_elem = *it; + it_elem.push_back(' '); + it_elem += value; + } + + return *this; +} + +helmet::csp_policy::csp_policy() = default; +helmet::csp_policy::~csp_policy() = default; + +helmet::csp_policy& helmet::csp_policy::remove(const core::string_view& allow) +{ + if (auto it = std::find_if(list.cbegin(), list.cend(), [&allow](const std::string& in) { + std::string allow_with_space = std::string(allow) + " "; + return in.find(allow_with_space) != std::string::npos; + }); it != list.cend()) + { + list.erase(it); + } + else + { + detail::throw_invalid_argument(); + } + + return *this; +} + +option_pair x_xss_protection() +{ + return {"X-XSS-Protection", {"0"}}; +} + +option_pair x_content_type_options() +{ + return {"X-Content-Type-Options", {"nosniff"}}; +} + +option_pair content_security_policy(const helmet::csp_policy& sp) +{ + if (sp.list.empty()) + detail::throw_invalid_argument(); + + return {"Content-Security-Policy", sp.list}; +} + +option_pair x_frame_origin(const helmet_origin_type& origin) +{ + if (origin == helmet_origin_type::sameorigin) + { + return {"X-Frame-Options", {"SAMEORIGIN"}}; + } + else if (origin == helmet_origin_type::deny) + { + return {"X-Frame-Options", {"DENY"}}; + } + + return {}; +} + +option_pair x_download_options(const helmet_download_type& type) +{ + if (type == helmet_download_type::noopen) + { + return {"X-Download-Options", {"noopen"}}; + } + + return {}; +} + +option_pair cross_origin_opener_policy(const coop_policy_type& policy) +{ + std::string value; + + switch (policy) + { + case coop_policy_type::unsafe_none: + value = "unsafe-none"; + break; + case coop_policy_type::same_origin_allow_popups: + value = "same-origin-allow-popups"; + break; + case coop_policy_type::same_origin: + value = "same-origin"; + break; + default: + return {}; + } + + return {"Cross-Origin-Opener-Policy", {value}}; +} + +option_pair cross_origin_resource_policy(const corp_policy_type& policy) +{ + std::string value; + + switch (policy) + { + case corp_policy_type::same_origin: + value = "same-origin"; + break; + case corp_policy_type::same_site: + value = "same-site"; + break; + case corp_policy_type::cross_origin: + value = "cross-origin"; + break; + default: + return {}; + } + + return {"Cross-Origin-Resource-Policy", {value}}; +} + +option_pair cross_origin_embedder_policy(const coep_policy_type& policy) +{ + std::string value; + + switch (policy) + { + case coep_policy_type::unsafe_none: + value = "unsafe-none"; + break; + case coep_policy_type::require_corp: + value = "require-corp"; + break; + case coep_policy_type::credentialless: + value = "credentialless"; + break; + default: + return {}; + } + + return {"Cross-Origin-Embedder-Policy", {value}}; +} + +option_pair strict_transport_security(std::size_t age, bool include_domains, bool preload) +{ + std::string value = "max-age=" + std::to_string(age); + + if (include_domains) + { + value += "; includeSubDomains"; + } + + if (preload) + { + value += "; preload"; + } + + return {"Strict-Transport-Security", {value}}; +} + +option_pair referrer_policy(const referrer_policy_type& policy) +{ + std::string value; + + switch (policy) + { + case referrer_policy_type::no_referrer: + value = "no-referrer"; + break; + case referrer_policy_type::no_referrer_when_downgrade: + value = "no-referrer-when-downgrade"; + break; + case referrer_policy_type::same_origin: + value = "same-origin"; + break; + case referrer_policy_type::origin: + value = "origin"; + break; + case referrer_policy_type::strict_origin: + value = "strict-origin"; + break; + case referrer_policy_type::origin_when_cross_origin: + value = "origin-when-cross-origin"; + break; + case referrer_policy_type::strict_origin_when_cross_origin: + value = "strict-origin-when-cross-origin"; + break; + case referrer_policy_type::unsafe_url: + value = "unsafe-url"; + break; + default: + return {}; + } + + return {"Referrer-Policy", {value}}; +} + +option_pair origin_agent_cluster() +{ + return {"Origin-Agent-Cluster", {"?1"}}; +} + +option_pair dns_prefetch_control(bool allow) +{ + return {"X-DNS-Prefetch-Control", {allow ? "on" : "off"}}; +} + +option_pair permitted_cross_domain_policies(const cross_domain_policy_type& policy) +{ + std::string value; + + switch (policy) + { + case cross_domain_policy_type::none: + value = "none"; + break; + case cross_domain_policy_type::master_only: + value = "master-only"; + break; + case cross_domain_policy_type::by_content_type: + value = "by-content-type"; + break; + case cross_domain_policy_type::by_ftp_filename: + value = "by-ftp-filename"; + break; + case cross_domain_policy_type::all: + value = "all"; + break; + default: + return {}; + } + + return {"X-Permitted-Cross-Domain-Policies", {value}}; +} + +option_pair hide_powered_by() +{ + return {"X-Powered-By", {}}; +} +} + +} diff --git a/src/server/route_handler.cpp b/src/server/route_handler.cpp index 26275fbe..9e60beed 100644 --- a/src/server/route_handler.cpp +++ b/src/server/route_handler.cpp @@ -9,8 +9,6 @@ #include #include -#include -#include namespace boost { namespace http_proto { @@ -56,24 +54,35 @@ set_body(std::string s) auto route_params:: spawn( - capy::task) -> + capy::task t) -> route_result { - detail::throw_invalid_argument(); -} + return this->suspend( + [ex = this->ex, t = std::move(t)](resumer resume) mutable + { + auto h = t.release(); -#endif + h.promise().on_done = [resume, h]() + { + auto& r = h.promise().result; + if(r.index() == 2) + { + auto ep = std::get<2>(r); + h.destroy(); + resume(ep); + return; + } + auto rv = std::move(std::get<1>(r)); + auto resume_ = resume; // would be destroyed + h.destroy(); + resume_(rv); + }; -void -route_params:: -do_post() -{ - BOOST_ASSERT(task_); - // invoke until task resumes - for(;;) - if(task_->invoke()) - break; + ex.post([h]() { h.resume(); }); + }); } +#endif + } // http_proto } // boost diff --git a/src/server/router_types.cpp b/src/server/router_types.cpp index fec7ceb9..21cfe625 100644 --- a/src/server/router_types.cpp +++ b/src/server/router_types.cpp @@ -21,7 +21,7 @@ const char* route_cat_type:: name() const noexcept { - return "boost.http"; + return "boost.http.route"; } std::string @@ -40,12 +40,12 @@ message( { switch(static_cast(code)) { - case route::close: return "route::close"; - case route::complete: return "route::complete"; - case route::suspend: return "route::suspend"; - case route::next: return "route::next"; - case route::next_route: return "route::next_route"; - case route::send: return "route::send"; + case route::close: return "close"; + case route::complete: return "complete"; + case route::suspend: return "suspend"; + case route::next: return "next"; + case route::next_route: return "next_route"; + case route::send: return "send"; default: return "?"; } diff --git a/test/cmake_test/CMakeLists.txt b/test/cmake_test/CMakeLists.txt index c43b6145..9c90abf0 100644 --- a/test/cmake_test/CMakeLists.txt +++ b/test/cmake_test/CMakeLists.txt @@ -16,40 +16,8 @@ if(BOOST_CI_INSTALL_TEST) find_package(Boost CONFIG REQUIRED COMPONENTS http_proto) else() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - add_subdirectory(../.. boostorg/http_proto) - - set(BOOST_URL_BUILD_TESTS OFF CACHE BOOL "" FORCE) - - set(deps - # Primary dependencies - - assert - buffers - capy - config - core - static_assert - system - throw_exception - type_traits - url - winapi - - # Secondary dependencies - - compat - container_hash - mp11 - variant2 - align - optional - predef - describe - ) - - foreach(dep IN LISTS deps) - add_subdirectory(../../../${dep} boostorg/${dep} EXCLUDE_FROM_ALL) - endforeach() + set(BOOST_INCLUDE_LIBRARIES http_proto) + add_subdirectory(../../../.. boostorg/boost) endif() add_executable(main main.cpp) diff --git a/test/unit/detail/file_posix.cpp b/test/unit/detail/file_posix.cpp deleted file mode 100644 index 5b9791f8..00000000 --- a/test/unit/detail/file_posix.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -// Test that header file is self-contained. -#include - -#if BOOST_HTTP_PROTO_USE_POSIX_FILE - -#include "file_test.hpp" - -namespace boost { -namespace http_proto { -namespace detail { - -class file_posix_test -{ -public: - void - run() - { - test_file(); - } -}; - -TEST_SUITE( - file_posix_test, - "boost.http_proto.detail.file_posix"); - -} // detail -} // http_proto -} // boost - -#endif diff --git a/test/unit/detail/file_stdio.cpp b/test/unit/detail/file_stdio.cpp deleted file mode 100644 index 9e09d672..00000000 --- a/test/unit/detail/file_stdio.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -// Test that header file is self-contained. -#include - -#include "file_test.hpp" - -namespace boost { -namespace http_proto { -namespace detail { - -class file_stdio_test -{ -public: - void - run() - { -#ifdef _WIN32 - test_file(); -#else - test_file(); -#endif - } -}; - -TEST_SUITE( - file_stdio_test, - "boost.http_proto.detail.file_stdio"); - -} // detail -} // http_proto -} // boost diff --git a/test/unit/detail/file_win32.cpp b/test/unit/detail/file_win32.cpp deleted file mode 100644 index 7be66613..00000000 --- a/test/unit/detail/file_win32.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -// Test that header file is self-contained. -#include - -#if BOOST_HTTP_PROTO_USE_WIN32_FILE - -#include "file_test.hpp" - -namespace boost { -namespace http_proto { -namespace detail { - -class file_win32_test -{ -public: - void - run() - { - test_file(); - } -}; - -TEST_SUITE( - file_win32_test, - "boost.http_proto.detail.file_win32"); - -} // detail -} // http_proto -} // boost - -#endif // BOOST_HTTP_PROTO_USE_WIN32_FILE diff --git a/test/unit/file.cpp b/test/unit/file.cpp deleted file mode 100644 index 703c66fd..00000000 --- a/test/unit/file.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2025 Mohammad Nejati -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -// Test that header file is self-contained. -#include - -#include -#include "file_test.hpp" - -namespace boost { -namespace http_proto { - -struct file_test -{ - void - testThrowingOverloads() - { - // constructor - BOOST_TEST_THROWS( - file("missing.txt", file_mode::scan), - system::system_error); - - file f; - char buf[1]; - - BOOST_TEST_THROWS( - f.open("missing.txt", file_mode::scan), - system::system_error); - // BOOST_TEST_THROWS( - // f.close(), - // system::system_error); - BOOST_TEST_THROWS( - f.size(), - system::system_error); - BOOST_TEST_THROWS( - f.pos(), - system::system_error); - BOOST_TEST_THROWS( - f.seek(1), - system::system_error); - BOOST_TEST_THROWS( - f.read(buf, 1), - system::system_error); - BOOST_TEST_THROWS( - f.write(buf, 1), - system::system_error); - } - - void - run() - { - test_file(); - testThrowingOverloads(); - } -}; - -TEST_SUITE( - file_test, - "boost.http_proto.file"); - -} // http_proto -} // boost diff --git a/test/unit/file_sink.cpp b/test/unit/file_sink.cpp index f900e9fa..e73da841 100644 --- a/test/unit/file_sink.cpp +++ b/test/unit/file_sink.cpp @@ -57,7 +57,7 @@ struct file_sink_test { // passing a closed file { - file f; + capy::file f; file_sink fsink(std::move(f)); buffers::const_buffer cb("123", 3); auto rs = fsink.write(cb, true); @@ -70,9 +70,9 @@ struct file_sink_test testWrite() { temp_path path; - file f; + capy::file f; system::error_code ec; - f.open(path, file_mode::write, ec); + f.open(path, capy::file_mode::write, ec); BOOST_TEST(!ec); BOOST_TEST(f.is_open()); file_sink fsink(std::move(f)); diff --git a/test/unit/file_source.cpp b/test/unit/file_source.cpp index aae19b10..1f52e272 100644 --- a/test/unit/file_source.cpp +++ b/test/unit/file_source.cpp @@ -58,7 +58,7 @@ struct file_source_test { // passing a closed file { - file f; + capy::file f; file_source fsource(std::move(f)); char buf[16] = {}; auto rs = fsource.read( @@ -74,9 +74,9 @@ struct file_source_test { temp_path path; write_file(path, "Hello, World!"); - file f; + capy::file f; system::error_code ec; - f.open(path, file_mode::read, ec); + f.open(path, capy::file_mode::read, ec); BOOST_TEST(!ec); BOOST_TEST(f.is_open()); file_source fsource(std::move(f)); @@ -101,9 +101,9 @@ struct file_source_test { temp_path path; write_file(path, "Hello, World!"); - file f; + capy::file f; system::error_code ec; - f.open(path, file_mode::read, ec); + f.open(path, capy::file_mode::read, ec); file_source fsource( std::move(f), 6); // Bounded to 6 bytes diff --git a/test/unit/file_test.hpp b/test/unit/file_test.hpp deleted file mode 100644 index b4abb646..00000000 --- a/test/unit/file_test.hpp +++ /dev/null @@ -1,469 +0,0 @@ -// -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_FILE_TEST_HPP -#define BOOST_HTTP_PROTO_FILE_TEST_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "test_suite.hpp" - -#if defined(BOOST_GCC) && BOOST_GCC >= 130000 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wself-move" -#endif - -namespace boost { -namespace http_proto { - -template -void -test_file() -{ - BOOST_STATIC_ASSERT( - ! std::is_copy_constructible::value); - BOOST_STATIC_ASSERT( - ! std::is_copy_assignable::value); - - namespace fs = boost::filesystem; - -#ifdef _WIN32 - static - constexpr - boost::winapi::WCHAR_ - unicode_suffix[] = { - 0xd83e, 0xdd84, 0x0000 }; // UTF-16-LE unicorn -#else - static - constexpr - char - unicode_suffix[] = { - '\xf0', '\x9f', '\xa6', '\x84', '\x00' }; // UTF-8 unicorn -#endif - - class temp_path - { - fs::path path_; - std::vector utf8_str_; - - public: - temp_path() - : path_(fs::unique_path()) - { - if (append_unicode_suffix) - path_ += unicode_suffix; -#ifdef _WIN32 - constexpr auto cp = boost::winapi::CP_UTF8_; - constexpr auto flags = boost::winapi::WC_ERR_INVALID_CHARS_; - auto sz = boost::winapi::WideCharToMultiByte( - cp, flags, path_.c_str(), -1, nullptr, 0, - nullptr, nullptr); - BOOST_TEST(sz != 0); - utf8_str_.resize(sz); - auto ret = boost::winapi::WideCharToMultiByte( - cp, flags, path_.c_str(), -1, - utf8_str_.data(), sz, - nullptr, nullptr); - BOOST_TEST(ret == sz); -#endif - } - - operator fs::path const&() - { - return path_; - } - - operator char const*() - { -#ifdef _WIN32 - return utf8_str_.data(); -#else - return path_.c_str(); -#endif - } - }; - - auto const create = - [](fs::path const& path, std::string const& data = "") - { - BOOST_TEST(! fs::exists(path)); - std::ofstream out(path.c_str()); - BOOST_TEST(out.is_open()); - if (data.size()) - out.write(data.c_str(), data.size()); - }; - - auto const remove = - [](fs::path const& path) - { - fs::remove(path); - BOOST_TEST(! fs::exists(path)); - }; - - auto const consume_file = - [](fs::path const& path) - { - // no exceptions - failure will result in an empty string - std::ifstream in(path.c_str()); - noskipws(in); - auto s = std::string( - std::istream_iterator(in), - std::istream_iterator()); - in.close(); - return s; - }; - - temp_path path; - - // bad file descriptor - { - File f; - char buf[1]; - BOOST_TEST(! f.is_open()); - BOOST_TEST(! fs::exists(path)); - { - system::error_code ec; - f.size(ec); - BOOST_TEST(ec == - system::errc::bad_file_descriptor); - } - { - system::error_code ec; - f.pos(ec); - BOOST_TEST(ec == - system::errc::bad_file_descriptor); - } - { - system::error_code ec; - f.seek(0, ec); - BOOST_TEST(ec == - system::errc::bad_file_descriptor); - } - { - system::error_code ec; - f.read(buf, 0, ec); - BOOST_TEST(ec == - system::errc::bad_file_descriptor); - } - { - system::error_code ec; - f.write(buf, 0, ec); - BOOST_TEST(ec == - system::errc::bad_file_descriptor); - } - } - - // file_mode::read - { - { - File f; - system::error_code ec; - create(path); - f.open(path, file_mode::read, ec); - BOOST_TEST(! ec); - } - remove(path); - } - - // file_mode::scan - { - { - File f; - system::error_code ec; - create(path); - f.open(path, file_mode::scan, ec); - BOOST_TEST(! ec); - } - remove(path); - } - - // file_mode::write - { - { - File f; - system::error_code ec; - BOOST_TEST(! fs::exists(path)); - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - BOOST_TEST(fs::exists(path)); - } - { - File f; - system::error_code ec; - BOOST_TEST(fs::exists(path)); - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - BOOST_TEST(fs::exists(path)); - } - remove(path); - } - - // file_mode::write_new - { - { - File f; - system::error_code ec; - BOOST_TEST(! fs::exists(path)); - f.open(path, file_mode::write_new, ec); - BOOST_TEST(! ec); - BOOST_TEST(fs::exists(path)); - } - { - File f; - system::error_code ec; - BOOST_TEST(fs::exists(path)); - f.open(path, file_mode::write_new, ec); - BOOST_TEST(ec); - } - remove(path); - } - - // file_mode::write_existing - { - { - File f; - system::error_code ec; - BOOST_TEST(! fs::exists(path)); - f.open(path, file_mode::write_existing, ec); - BOOST_TEST(ec); - BOOST_TEST(! fs::exists(path)); - } - { - File f; - system::error_code ec; - create(path); - BOOST_TEST(fs::exists(path)); - f.open(path, file_mode::write_existing, ec); - BOOST_TEST(! ec); - } - remove(path); - } - - // file_mode::append - { - { - File f; - system::error_code ec; - BOOST_TEST(! fs::exists(path)); - f.open(path, file_mode::append, ec); - BOOST_TEST(! ec); - BOOST_TEST(fs::exists(path)); - static const std::string extra = "the"; - f.write(extra.c_str(), extra.size(), ec); - BOOST_TEST(!ec); - f.close(ec); - auto s = consume_file(path); - BOOST_TEST(s == "the"); - } - - { - File f; - system::error_code ec; - BOOST_TEST(fs::exists(path)); - f.open(path, file_mode::append, ec); - BOOST_TEST(! ec); - BOOST_TEST(fs::exists(path)); - static const std::string extra = " cat"; - f.write(extra.c_str(), extra.size(), ec); - BOOST_TEST(!ec); - f.close(ec); - auto s = consume_file(path); - BOOST_TEST_EQ(s, "the cat"); - } - remove(path); - } - - // file_mode::append_existing - { - { - File f; - system::error_code ec; - BOOST_TEST(! fs::exists(path)); - f.open(path, file_mode::append_existing, ec); - BOOST_TEST(ec); - BOOST_TEST(! fs::exists(path)); - } - remove(path); - { - File f; - system::error_code ec; - create(path, "the cat"); - f.open(path, file_mode::append_existing, ec); - BOOST_TEST(! ec); - static std::string const extra = " sat"; - f.write(extra.c_str(), extra.size(), ec); - BOOST_TEST(!ec); - f.close(ec); - BOOST_TEST(!ec); - auto s = consume_file(path); - BOOST_TEST_EQ(s, "the cat sat"); - } - remove(path); - } - - // special members - { - { - File f1; - system::error_code ec; - f1.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - BOOST_TEST(f1.is_open()); - - // move constructor - File f2(std::move(f1)); - BOOST_TEST(! f1.is_open()); - BOOST_TEST(f2.is_open()); - - // move assignment - File f3; - f3 = std::move(f2); - BOOST_TEST(! f2.is_open()); - BOOST_TEST(f3.is_open()); - } - remove(path); - } - - // re-open - { - { - File f; - system::error_code ec; - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - } - remove(path); - } - - // re-assign - { - temp_path path2; - { - system::error_code ec; - - File f1; - f1.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - - File f2; - f2.open(path2, file_mode::write, ec); - BOOST_TEST(! ec); - - f2 = std::move(f1); - BOOST_TEST(! f1.is_open()); - BOOST_TEST(f2.is_open()); - } - remove(path); - remove(path2); - } - - // self-move - { - { - File f; - system::error_code ec; - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - auto& f_(f); - f_ = std::move(f); - BOOST_TEST(f.is_open()); - } - remove(path); - } - - // native_handle - { - { - File f; - auto none = f.native_handle(); - system::error_code ec; - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - auto fd = f.native_handle(); - BOOST_TEST(fd != none); - f.native_handle(none); - BOOST_TEST(! f.is_open()); - } - remove(path); - } - - // read and write - { - core::string_view const s = - "Hello, world!"; - - // write - { - File f; - system::error_code ec; - f.open(path, file_mode::write, ec); - BOOST_TEST(! ec); - - f.write(s.data(), s.size(), ec); - BOOST_TEST(! ec); - - auto size = f.size(ec); - BOOST_TEST(! ec); - BOOST_TEST(size == s.size()); - - auto pos = f.pos(ec); - BOOST_TEST(! ec); - BOOST_TEST(pos == size); - - f.close(ec); - BOOST_TEST(! ec); - } - - // read - { - File f; - system::error_code ec; - f.open(path, file_mode::read, ec); - BOOST_TEST(! ec); - - std::string buf; - buf.resize(s.size()); - f.read(&buf[0], buf.size(), ec); - BOOST_TEST(! ec); - BOOST_TEST(buf == s); - - f.seek(1, ec); - BOOST_TEST(! ec); - buf.resize(3); - f.read(&buf[0], buf.size(), ec); - BOOST_TEST(! ec); - BOOST_TEST(buf == "ell"); - - auto pos = f.pos(ec); - BOOST_TEST(! ec); - BOOST_TEST(pos == 4); - } - remove(path); - } - - BOOST_TEST(! fs::exists(path)); -} - -} // http_proto -} // boost - -#endif diff --git a/test/unit/file_mode.cpp b/test/unit/json.cpp similarity index 72% rename from test/unit/file_mode.cpp rename to test/unit/json.cpp index 5ff75fa6..1c87c680 100644 --- a/test/unit/file_mode.cpp +++ b/test/unit/json.cpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,4 +8,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/unit/server/helmet.cpp b/test/unit/server/helmet.cpp new file mode 100644 index 00000000..530e4907 --- /dev/null +++ b/test/unit/server/helmet.cpp @@ -0,0 +1,300 @@ +// +// Copyright (c) 2025 Amlal El Mahrouss (amlal at nekernel dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#include +#include "test_suite.hpp" + +namespace boost { +namespace http_proto { + +struct helmet_test +{ + void + run() + { + // Test X-Download-Options + { + helmet_options opt; + + opt.set(x_download_options(helmet_download_type::noopen)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-Download-Options") > 0); + } + + // Test X-Frame-Options with DENY + { + helmet_options opt; + + opt.set(x_frame_origin(helmet_origin_type::deny)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-Frame-Options") > 0); + } + + // Test X-Frame-Options with SAMEORIGIN + { + helmet_options opt; + + opt.set(x_frame_origin(helmet_origin_type::sameorigin)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-Frame-Options") > 0); + } + + // Test X-XSS-Protection + { + helmet_options opt; + + opt.set(x_xss_protection()); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-XSS-Protection") > 0); + } + + // Test X-Content-Type-Options + { + helmet_options opt; + + opt.set(x_content_type_options()); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-Content-Type-Options") > 0); + } + + // Test Strict-Transport-Security with defaults + { + helmet_options opt; + + opt.set(strict_transport_security(hsts::default_age)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Strict-Transport-Security") > 0); + } + + // Test Strict-Transport-Security with custom options + { + helmet_options opt; + + opt.set(strict_transport_security(86400, false, false)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Strict-Transport-Security") > 0); + } + + // Test Cross-Origin-Opener-Policy + { + helmet_options opt; + + opt.set(cross_origin_opener_policy(coop_policy_type::same_origin)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Cross-Origin-Opener-Policy") > 0); + } + + // Test Cross-Origin-Resource-Policy + { + helmet_options opt; + + opt.set(cross_origin_resource_policy(corp_policy_type::same_origin)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Cross-Origin-Resource-Policy") > 0); + } + + // Test Cross-Origin-Embedder-Policy + { + helmet_options opt; + + opt.set(cross_origin_embedder_policy(coep_policy_type::require_corp)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Cross-Origin-Embedder-Policy") > 0); + } + + // Test Referrer-Policy + { + helmet_options opt; + + opt.set(referrer_policy(referrer_policy_type::no_referrer)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Referrer-Policy") > 0); + } + + // Test Origin-Agent-Cluster + { + helmet_options opt; + + opt.set(origin_agent_cluster()); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Origin-Agent-Cluster") > 0); + } + + // Test X-DNS-Prefetch-Control + { + helmet_options opt; + + opt.set(dns_prefetch_control(false)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-DNS-Prefetch-Control") > 0); + } + + // Test X-Permitted-Cross-Domain-Policies + { + helmet_options opt; + + opt.set(permitted_cross_domain_policies(cross_domain_policy_type::none)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("X-Permitted-Cross-Domain-Policies") > 0); + } + + // Test hide_powered_by + { + helmet_options opt; + + opt.set(hide_powered_by()); + + helmet helmet{opt}; + route_params p; + + // Add X-Powered-By header first + p.res.set("X-Powered-By", "Express"); + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + // After helmet middleware, X-Powered-By should be removed + BOOST_TEST(p.res.count("X-Powered-By") == 0); + } + + // Test Content-Security-Policy with CSP builder + { + helmet_options opt; + helmet::csp_policy csp; + + csp.allow("default-src", csp_type::self) + .allow("script-src", csp_type::self) + .allow("style-src", csp_type::self); + + opt.set(content_security_policy(csp)); + + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + BOOST_TEST(p.res.count("Content-Security-Policy") > 0); + } + + // Test default helmet configuration + { + helmet_options opt; + helmet helmet{opt}; + route_params p; + + auto ec = helmet(p); + + BOOST_TEST(ec == route::next); + // Check that default headers are set + BOOST_TEST(p.res.count("X-Download-Options") > 0); + BOOST_TEST(p.res.count("X-Frame-Options") > 0); + BOOST_TEST(p.res.count("X-XSS-Protection") > 0); + BOOST_TEST(p.res.count("X-Content-Type-Options") > 0); + BOOST_TEST(p.res.count("Strict-Transport-Security") > 0); + BOOST_TEST(p.res.count("Cross-Origin-Opener-Policy") > 0); + BOOST_TEST(p.res.count("Cross-Origin-Resource-Policy") > 0); + BOOST_TEST(p.res.count("Origin-Agent-Cluster") > 0); + BOOST_TEST(p.res.count("Referrer-Policy") > 0); + BOOST_TEST(p.res.count("X-DNS-Prefetch-Control") > 0); + BOOST_TEST(p.res.count("X-Permitted-Cross-Domain-Policies") > 0); + } + } +}; + +TEST_SUITE( + helmet_test, + "boost.http_proto.server.helmet"); +} + +} diff --git a/test/unit/server/router_types.cpp b/test/unit/server/router_types.cpp index 297df6ed..78676bc9 100644 --- a/test/unit/server/router_types.cpp +++ b/test/unit/server/router_types.cpp @@ -18,9 +18,39 @@ namespace http_proto { struct router_types_test { + template + void + check( + char const* name, + Error ev) + { + auto const ec = make_error_code(ev); + BOOST_TEST(std::string(ec.category().name()) == name); + BOOST_TEST(! ec.message().empty()); + BOOST_TEST( + std::addressof(ec.category()) == + std::addressof(make_error_code(ev).category())); + BOOST_TEST(ec.category().equivalent( + static_cast::type>(ev), + ec.category().default_error_condition( + static_cast::type>(ev)))); + BOOST_TEST(ec.category().equivalent(ec, + static_cast::type>(ev))); + } + void run() { + { + char const* const n = "boost.http.route"; + check(n, route::close); + check(n, route::complete); + check(n, route::suspend); + check(n, route::next); + check(n, route::next_route); + check(n, route::send); + } + basic_router r; r.add(http_proto::method::post, "/", [](route_params_base& rp) ->