diff --git a/.drone.star b/.drone.star index 77e832ac..cae42f51 100644 --- a/.drone.star +++ b/.drone.star @@ -15,7 +15,7 @@ def main(ctx): # Compilers [ 'gcc >=5.0', - 'clang >=3.8', + 'clang >=3.9', 'msvc >=14.1', 'arm64-gcc latest', 's390x-gcc latest', @@ -23,11 +23,12 @@ def main(ctx): 'apple-clang *', 'arm64-clang latest', 's390x-clang latest', - # 'x86-msvc latest' + 'freebsd-clang latest', + 'x86-msvc latest' ], # Standards '>=11', - packages=['zlib1g', 'zlib1g-dev']) + packages=['zlib1g', 'zlib1g-dev', 'libbrotli-dev']) # from https://github.com/cppalliance/ci-automation load("@ci_automation//ci/drone/:functions.star", "linux_cxx", "windows_cxx", "osx_cxx", "freebsd_cxx", "generate") diff --git a/.drone/drone.bat b/.drone/drone.bat index 441a3332..580ffd1e 100755 --- a/.drone/drone.bat +++ b/.drone/drone.bat @@ -43,7 +43,10 @@ echo using zlib : : : ^off ^; >> !BOOST_ROOT!\project-config.jam REM Customizations cd pushd !BOOST_ROOT!\libs -git clone https://github.com/CPPAlliance/buffers -b !BOOST_BRANCH! +git clone https://github.com/cppalliance/buffers -b !BOOST_BRANCH! --depth 1 +popd +pushd !BOOST_ROOT!\libs +git clone https://github.com/cppalliance/rts -b !BOOST_BRANCH! --depth 1 popd echo '==================================> COMPILE' diff --git a/.drone/drone.sh b/.drone/drone.sh index 27c11ed4..ee11fc3a 100755 --- a/.drone/drone.sh +++ b/.drone/drone.sh @@ -37,7 +37,13 @@ common_install () { if [ ! -d "$BOOST_ROOT/libs/buffers" ]; then pushd $BOOST_ROOT/libs - git clone https://github.com/CPPAlliance/buffers -b $BOOST_BRANCH --depth 1 + git clone https://github.com/cppalliance/buffers -b $BOOST_BRANCH --depth 1 + popd + fi + + if [ ! -d "$BOOST_ROOT/libs/rts" ]; then + pushd $BOOST_ROOT/libs + git clone https://github.com/cppalliance/rts -b $BOOST_BRANCH --depth 1 popd fi } @@ -117,10 +123,14 @@ git submodule update --init --recursive # Customizations if [ ! -d "$BOOST_ROOT/libs/buffers" ]; then pushd $BOOST_ROOT/libs - git clone https://github.com/CPPAlliance/buffers -b $BOOST_BRANCH --depth 1 + git clone https://github.com/cppalliance/buffers -b $BOOST_BRANCH --depth 1 + popd +fi +if [ ! -d "$BOOST_ROOT/libs/rts" ]; then + pushd $BOOST_ROOT/libs + git clone https://github.com/cppalliance/rts -b $BOOST_BRANCH --depth 1 popd fi - cd libs/$SELF mkdir __build__ && cd __build__ cmake -DCMAKE_INSTALL_PREFIX=~/.local .. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19084390..2bfefc51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,14 +62,14 @@ jobs: # - compiler: "msvc" - version: "14.34" + version: "14.42" cxxstd: "17,20" latest-cxxstd: "20" runs-on: "windows-2022" - b2-toolset: "msvc-14.3" + b2-toolset: "msvc-14.4" generator: "Visual Studio 17 2022" is-latest: true - name: "MSVC 14.34: C++17-20" + name: "MSVC 14.42: C++17-20" build-type: "Release" build-cmake: true @@ -81,10 +81,9 @@ jobs: b2-toolset: "msvc-14.3" generator: "Visual Studio 17 2022" is-latest: true - name: "MSVC 14.34: C++17-20 (no zlib)" + name: "MSVC 14.34: C++17-20" build-type: "Release" build-cmake: true - skip-zlib: true - compiler: "msvc" version: "14.34" @@ -111,17 +110,6 @@ jobs: build-type: "Release" build-cmake: true - - compiler: "msvc" - version: "14.29" - cxxstd: "14,17" - latest-cxxstd: "17" - runs-on: "windows-2019" - b2-toolset: "msvc-14.2" - generator: "Visual Studio 16 2019" - is-earliest: true - name: "MSVC 14.29: C++14-17" - build-type: "Release" - - compiler: "clang-cl" version: "*" cxx: "clang++-cl" @@ -233,7 +221,7 @@ jobs: is-latest: true name: "GCC 14: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" build-cmake: true - compiler: "gcc" @@ -263,7 +251,7 @@ jobs: name: "GCC 14: C++17-20 (x86)" x86: true build-type: "Release" - install: "gcc-14-multilib g++-14-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-14-multilib g++-14-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "gcc" version: "14" @@ -278,7 +266,7 @@ jobs: name: "GCC 14: C++17-20 (shared)" shared: true build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" build-cmake: true - compiler: "gcc" @@ -295,7 +283,7 @@ jobs: shared: true x86: true build-type: "Release" - install: "gcc-14-multilib g++-14-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-14-multilib g++-14-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" build-cmake: true - compiler: "gcc" @@ -311,7 +299,7 @@ jobs: name: "GCC 14: C++17-20 (asan)" asan: true build-type: "RelWithDebInfo" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "14" @@ -327,7 +315,7 @@ jobs: asan: true x86: true build-type: "RelWithDebInfo" - install: "gcc-14-multilib g++-14-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-14-multilib g++-14-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "gcc" version: "14" @@ -342,7 +330,7 @@ jobs: name: "GCC 14: C++17-20 (ubsan)" ubsan: true build-type: "RelWithDebInfo" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "14" @@ -358,7 +346,7 @@ jobs: ubsan: true x86: true build-type: "RelWithDebInfo" - install: "gcc-14-multilib g++-14-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-14-multilib g++-14-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "gcc" version: "13" @@ -370,7 +358,7 @@ jobs: b2-toolset: "gcc" name: "GCC 13: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "13" @@ -386,7 +374,7 @@ jobs: build-type: "Debug" cxxflags: "--coverage -fprofile-arcs -ftest-coverage" ccflags: "--coverage -fprofile-arcs -ftest-coverage" - install: "lcov zlib1g-dev wget unzip" + install: "lcov zlib1g-dev libbrotli-dev wget unzip" - compiler: "gcc" version: "12" @@ -399,7 +387,7 @@ jobs: b2-toolset: "gcc" name: "GCC 12: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "11" @@ -412,7 +400,7 @@ jobs: b2-toolset: "gcc" name: "GCC 11: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "10" @@ -425,7 +413,7 @@ jobs: b2-toolset: "gcc" name: "GCC 10: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "9" @@ -438,7 +426,7 @@ jobs: b2-toolset: "gcc" name: "GCC 9: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "8" @@ -451,7 +439,7 @@ jobs: b2-toolset: "gcc" name: "GCC 8: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "7" @@ -464,7 +452,7 @@ jobs: b2-toolset: "gcc" name: "GCC 7: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "6" @@ -477,7 +465,7 @@ jobs: b2-toolset: "gcc" name: "GCC 6: C++11-14" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "gcc" version: "5" @@ -491,7 +479,7 @@ jobs: is-earliest: true name: "GCC 5: C++11" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "18" @@ -505,7 +493,7 @@ jobs: is-latest: true name: "Clang 18: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" build-cmake: true - compiler: "clang" @@ -521,7 +509,7 @@ jobs: name: "Clang 18: C++17-20 (x86)" x86: true build-type: "Release" - install: "gcc-multilib g++-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-multilib g++-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "clang" version: "18" @@ -538,7 +526,7 @@ jobs: build-type: "Release" cxxflags: "-ftime-trace" ccflags: "-ftime-trace" - install: "zlib1g-dev wget unzip" + install: "zlib1g-dev libbrotli-dev wget unzip" - compiler: "clang" version: "18" @@ -553,7 +541,7 @@ jobs: name: "Clang 18: C++17-20 (asan)" asan: true build-type: "RelWithDebInfo" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "18" @@ -569,7 +557,7 @@ jobs: asan: true x86: true build-type: "RelWithDebInfo" - install: "gcc-multilib g++-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-multilib g++-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "clang" version: "18" @@ -584,7 +572,7 @@ jobs: name: "Clang 18: C++17-20 (ubsan)" ubsan: true build-type: "RelWithDebInfo" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "18" @@ -600,7 +588,7 @@ jobs: ubsan: true x86: true build-type: "RelWithDebInfo" - install: "gcc-multilib g++-multilib zlib1g-dev zlib1g-dev:i386" + install: "gcc-multilib g++-multilib zlib1g-dev libbrotli-dev zlib1g-dev:i386 libbrotli-dev:i386" - compiler: "clang" version: "17" @@ -612,7 +600,7 @@ jobs: b2-toolset: "clang" name: "Clang 17: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "16" @@ -624,7 +612,7 @@ jobs: b2-toolset: "clang" name: "Clang 16: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "15" @@ -637,7 +625,7 @@ jobs: b2-toolset: "clang" name: "Clang 15: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "14" @@ -650,7 +638,7 @@ jobs: b2-toolset: "clang" name: "Clang 14: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "13" @@ -663,7 +651,7 @@ jobs: b2-toolset: "clang" name: "Clang 13: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "12" @@ -676,7 +664,7 @@ jobs: b2-toolset: "clang" name: "Clang 12: C++17-20" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "11" @@ -689,7 +677,7 @@ jobs: b2-toolset: "clang" name: "Clang 11: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "10" @@ -702,7 +690,7 @@ jobs: b2-toolset: "clang" name: "Clang 10: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "9" @@ -715,7 +703,7 @@ jobs: b2-toolset: "clang" name: "Clang 9: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "8" @@ -728,7 +716,7 @@ jobs: b2-toolset: "clang" name: "Clang 8: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "7" @@ -741,7 +729,7 @@ jobs: b2-toolset: "clang" name: "Clang 7: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "6" @@ -754,7 +742,7 @@ jobs: b2-toolset: "clang" name: "Clang 6: C++14-17" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "5" @@ -767,7 +755,7 @@ jobs: b2-toolset: "clang" name: "Clang 5: C++11-14" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "4" @@ -780,7 +768,7 @@ jobs: b2-toolset: "clang" name: "Clang 4: C++11-14" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" - compiler: "clang" version: "3.9" @@ -794,7 +782,7 @@ jobs: is-earliest: true name: "Clang 3.9: C++11" build-type: "Release" - install: "zlib1g-dev" + install: "zlib1g-dev libbrotli-dev" name: ${{ matrix.name }} runs-on: ${{ fromJSON(needs.runner-selection.outputs.labelmatrix)[matrix.runs-on] }} @@ -870,19 +858,19 @@ jobs: buffers rts - - name: Install zlib (Windows) + - name: Install Packages (Windows) uses: alandefreitas/cpp-actions/package-install@v1.8.10 - if: ${{ startsWith(matrix.runs-on, 'windows') && !matrix.skip-zlib }} - id: zlib-install + if: ${{ startsWith(matrix.runs-on, 'windows') }} + id: package-install-windows with: - vcpkg: zlib + vcpkg: zlib brotli vcpkg-dir: vcpkg-root vcpkg-triplet: ${{ matrix.x86 && 'x86-windows-static' || 'x64-windows' }} - name: Patch user-config.jam (Windows) id: patch-user-config shell: bash - if: ${{ startsWith(matrix.runs-on, 'windows') && !matrix.skip-zlib }} + if: ${{ startsWith(matrix.runs-on, 'windows') }} run: | set -xe home=$(pwd) @@ -890,11 +878,15 @@ jobs: triplet=${{ matrix.x86 && 'x86-windows-static' || 'x64-windows' }} addrmdl=${{ matrix.x86 && '32' || '64' }} - echo "using zlib : : \"${home}/vcpkg-root/installed/${triplet}/include\" \"${home}/vcpkg-root/installed/${triplet}/lib\" zlib : ${addrmdl} ;" | sed 's/\/d\//D:\//g' > user-config.jam + # This is temporary until we move rts/build/brotli.jam to boost/tools/build + echo "import-search ${home}/boost-root/libs/rts/build ;" | sed 's/\/d\//D:\//g' >> user-config.jam + + echo "using zlib : : \"${home}/vcpkg-root/installed/${triplet}/include\" \"${home}/vcpkg-root/installed/${triplet}/lib\" \"${home}/vcpkg-root/installed/${triplet}/bin\" zlib : ${addrmdl} ;" | sed 's/\/d\//D:\//g' >> user-config.jam + echo "using brotli : : \"${home}/vcpkg-root/installed/${triplet}/include\" \"${home}/vcpkg-root/installed/${triplet}/lib\" \"${home}/vcpkg-root/installed/${triplet}/bin\" : ${addrmdl} ;" | sed 's/\/d\//D:\//g' >> user-config.jam + cat user-config.jam toolchain=$(echo "$GITHUB_WORKSPACE/vcpkg-root/scripts/buildsystems/vcpkg.cmake" | sed 's/\/d\//D:\//g' ) - cat ${toolchain} echo "toolchain=${toolchain}" >> $GITHUB_OUTPUT - name: ASLR Fix @@ -954,7 +946,7 @@ jobs: shared: ${{ matrix.shared }} rtti: ${{ (matrix.is-latest && 'on,off') || 'on' }} cxxflags: ${{ (matrix.asan && '-fsanitize-address-use-after-scope -fsanitize=pointer-subtract') || '' }} - user-config: ${{ (startsWith(matrix.runs-on, 'windows') && !matrix.skip-zlib && format('{0}/user-config.jam', steps.patch.outputs.workspace_root)) || '' }} + user-config: ${{ (startsWith(matrix.runs-on, 'windows') && format('{0}/user-config.jam', steps.patch.outputs.workspace_root)) || '' }} stop-on-error: true extra-args: ${{ (matrix.valgrind && 'testing.launcher=valgrind' || '' )}} diff --git a/build/Jamfile b/build/Jamfile index d4a113ff..62ced463 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -47,7 +47,6 @@ lib boost_http_proto : usage-requirements /boost//buffers /boost//rts - /boost//url ; boost-install boost_http_proto ; diff --git a/include/boost/http_proto/detail/align_up.hpp b/include/boost/http_proto/detail/align_up.hpp deleted file mode 100644 index 89510a20..00000000 --- a/include/boost/http_proto/detail/align_up.hpp +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2024 Christian Mazakas -// -// 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_ALIGN_UP_HPP -#define BOOST_HTTP_PROTO_DETAIL_ALIGN_UP_HPP - -#include - -namespace boost { -namespace http_proto { -namespace detail { - -constexpr -inline -std::size_t -align_up(std::size_t s, std::size_t A) -{ - return A * ( - (s + A - 1) / A); -} - -} // detail -} // http_proto -} // boost - -#endif diff --git a/include/boost/http_proto/detail/config.hpp b/include/boost/http_proto/detail/config.hpp index 0b36a667..d276f23b 100644 --- a/include/boost/http_proto/detail/config.hpp +++ b/include/boost/http_proto/detail/config.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2019 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) @@ -19,9 +20,6 @@ namespace http_proto { //------------------------------------------------ -#if defined(BOOST_HTTP_PROTO_DOCS) -# define BOOST_HTTP_PROTO_DECL -#else # if (defined(BOOST_HTTP_PROTO_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(BOOST_HTTP_PROTO_STATIC_LINK) # if defined(BOOST_HTTP_PROTO_SOURCE) # define BOOST_HTTP_PROTO_DECL BOOST_SYMBOL_EXPORT @@ -29,34 +27,12 @@ namespace http_proto { # else # define BOOST_HTTP_PROTO_DECL BOOST_SYMBOL_IMPORT # endif - -# if defined(BOOST_HTTP_PROTO_ZLIB_SOURCE) -# define BOOST_HTTP_PROTO_ZLIB_DECL BOOST_SYMBOL_EXPORT -# define BOOST_HTTP_PROTO_ZLIB_BUILD_DLL -# else -# define BOOST_HTTP_PROTO_ZLIB_DECL BOOST_SYMBOL_IMPORT -# endif - -# if defined(BOOST_HTTP_PROTO_EXT_SOURCE) -# define BOOST_HTTP_PROTO_EXT_DECL BOOST_SYMBOL_EXPORT -# define BOOST_HTTP_PROTO_EXT_BUILD_DLL -# else -# define BOOST_HTTP_PROTO_EXT_DECL BOOST_SYMBOL_IMPORT -# endif # endif // shared lib # ifndef BOOST_HTTP_PROTO_DECL # define BOOST_HTTP_PROTO_DECL # endif -# ifndef BOOST_HTTP_PROTO_ZLIB_DECL -# define BOOST_HTTP_PROTO_ZLIB_DECL -# endif - -# ifndef BOOST_HTTP_PROTO_EXT_DECL -# define BOOST_HTTP_PROTO_EXT_DECL -# endif - # if !defined(BOOST_HTTP_PROTO_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_HTTP_PROTO_NO_LIB) # define BOOST_LIB_NAME boost_http_proto # if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_HTTP_PROTO_DYN_LINK) @@ -64,7 +40,6 @@ namespace http_proto { # endif # include # endif -#endif //------------------------------------------------ @@ -74,17 +49,6 @@ namespace http_proto { # define BOOST_HTTP_PROTO_AGGREGATE_WORKAROUND #endif -// holds any offset within headers -using offset_type = ::uint32_t; // private - -// maximum size of http header, -// chunk header, or chunk extensions -#ifndef BOOST_HTTP_PROTO_MAX_HEADER -#define BOOST_HTTP_PROTO_MAX_HEADER (offset_type(-1)) -#endif -static constexpr auto max_offset = - BOOST_HTTP_PROTO_MAX_HEADER; - // Add source location to error codes #ifdef BOOST_HTTP_PROTO_NO_SOURCE_LOCATION # define BOOST_HTTP_PROTO_ERR(ev) (::boost::system::error_code(ev)) diff --git a/include/boost/http_proto/detail/header.hpp b/include/boost/http_proto/detail/header.hpp index 00b8e1a2..a106955f 100644 --- a/include/boost/http_proto/detail/header.hpp +++ b/include/boost/http_proto/detail/header.hpp @@ -11,7 +11,6 @@ #ifndef BOOST_HTTP_PROTO_DETAIL_HEADER_HPP #define BOOST_HTTP_PROTO_DETAIL_HEADER_HPP -#include #include #include #include @@ -52,6 +51,17 @@ struct header // ^ ^ ^ ^ // buf buf+prefix buf+size buf+cap +#ifdef BOOST_HTTP_PROTO_TEST_FORCE_8BIT_OFFSET + using offset_type = std::uint8_t; +#else + using offset_type = std::uint32_t; +#endif + + static + constexpr + std::size_t max_offset = + std::numeric_limits::max(); + struct entry { offset_type np; // name pos @@ -66,42 +76,6 @@ struct header std::size_t dv) const noexcept; }; - // HTTP-message = start-line CRLF *( field-line CRLF ) CRLF - // start-line = request-line / status-line - // status-line = HTTP-version SP status-code SP [ reason-phrase ] - // status-code = 3DIGIT - // HTTP-name = %x48.54.54.50 ; HTTP - // HTTP-version = HTTP-name "/" DIGIT "." DIGIT - // - // => "HTTP/1.1 111 \r\n" + trailing "\r\n" - static - constexpr - std::size_t const min_status_line = 17; - - // "X:\r\n" - static - constexpr - std::size_t const min_field_line = 4; - - static - constexpr - std::size_t const max_field_lines = - (max_offset - min_status_line) / min_field_line; - - /** Returns the largest permissible capacity in bytes - */ - static - constexpr - std::size_t - max_capacity_in_bytes() noexcept - { - // the entire serialized contents of the header - // must fit entirely in max_offset - return align_up( - (max_offset + (max_field_lines * sizeof(entry))), - alignof(entry)); - } - struct table { explicit @@ -148,7 +122,8 @@ struct header char const* cbuf = nullptr; char* buf = nullptr; std::size_t cap = 0; - std::size_t max_cap = max_capacity_in_bytes(); + std::size_t max_cap = + std::numeric_limits::max(); offset_type size = 0; offset_type count = 0; diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index dda0934d..30f37ff9 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -40,6 +40,13 @@ class fields_base detail::header h_; bool static_storage = false; + using entry = + detail::header::entry; + using offset_type = + detail::header::offset_type; + using table = + detail::header::table; + class op_t; class prefix_op_t { @@ -57,11 +64,6 @@ class fields_base ~prefix_op_t(); }; - using entry = - detail::header::entry; - using table = - detail::header::table; - friend class fields; template friend class static_fields; diff --git a/include/boost/http_proto/fields_view_base.hpp b/include/boost/http_proto/fields_view_base.hpp index f2ac22d7..8c402a2f 100644 --- a/include/boost/http_proto/fields_view_base.hpp +++ b/include/boost/http_proto/fields_view_base.hpp @@ -166,7 +166,7 @@ class fields_view_base std::size_t max_size() noexcept { - return max_offset; + return detail::header::max_offset; } /** Return an iterator to the beginning diff --git a/include/boost/http_proto/metadata.hpp b/include/boost/http_proto/metadata.hpp index 919946ab..967b5ff4 100644 --- a/include/boost/http_proto/metadata.hpp +++ b/include/boost/http_proto/metadata.hpp @@ -78,7 +78,12 @@ encoding /** * Indicates the body has gzip applied. */ - gzip + gzip, + + /** + * Indicates the body has brotli applied. + */ + br }; //------------------------------------------------ diff --git a/include/boost/http_proto/parser.hpp b/include/boost/http_proto/parser.hpp index 7cb779d8..8b273075 100644 --- a/include/boost/http_proto/parser.hpp +++ b/include/boost/http_proto/parser.hpp @@ -94,9 +94,18 @@ class parser */ std::uint64_t body_limit = 64 * 1024; + + /** True if parser can decode br Content-Encoding. + + The @ref rts::brotli::decode_service must already be + installed thusly, or else an exception + is thrown. + */ + bool apply_brotli_decoder = false; + /** True if parser can decode deflate Content-Encoding. - The @ref zlib::inflate_service must already be + The @ref rts::zlib::inflate_service must already be installed thusly, or else an exception is thrown. */ @@ -104,7 +113,7 @@ class parser /** True if parser can decode gzip Content-Encoding. - The @ref zlib::inflate_service must already be + The @ref zrts::lib::inflate_service must already be installed thusly, or else an exception is thrown. */ @@ -547,8 +556,6 @@ class parser private: friend class request_parser; friend class response_parser; - friend class parser_service; - class filter; BOOST_HTTP_PROTO_DECL parser(rts::context&, detail::kind); @@ -616,7 +623,7 @@ class parser buffers::mutable_buffer_pair mbp_; buffers::const_buffer_pair cbp_; - filter* filter_; + detail::filter* filter_; buffers::any_dynamic_buffer* eb_; sink* sink_; diff --git a/include/boost/http_proto/serializer.hpp b/include/boost/http_proto/serializer.hpp index e3afda9a..7cfed73a 100644 --- a/include/boost/http_proto/serializer.hpp +++ b/include/boost/http_proto/serializer.hpp @@ -35,6 +35,9 @@ namespace http_proto { #ifndef BOOST_HTTP_PROTO_DOCS class serializer_service; class message_view_base; +namespace detail { +class filter; +} // detail #endif /** A serializer for HTTP/1 messages @@ -71,6 +74,14 @@ class serializer */ struct config { + /** True if serializer can encode brotli Content-Encoding. + + The @ref rts::brotli::encode_service must already be + installed thusly, or else an exception + is thrown. + */ + bool apply_brotli_encoder = false; + /** True if serializer can encode deflate Content-Encoding. The @ref zlib::deflate_service must already be @@ -87,6 +98,20 @@ class serializer */ bool apply_gzip_encoder = false; + /** Specifies the brotli compression quality 0..11. + + Higher quality values result in better, but also + slower compression. + */ + std::uint32_t brotli_comp_quality = 5; + + /** Specifies the brotli compression window 10..24. + + Larger window sizes can improve compression + quality, but require more memory. + */ + std::uint32_t brotli_comp_window = 18; + /** Specifies the zlib compression level 0..9. A compression level of 1 provides the fastest speed, @@ -277,8 +302,6 @@ class serializer consume(std::size_t n); private: - friend class serializer_service; - class filter; class const_buf_gen_base; template class const_buf_gen; @@ -349,7 +372,7 @@ class serializer detail::workspace ws_; const_buf_gen_base* buf_gen_; - filter* filter_; + detail::filter* filter_; source* source_; buffers::circular_buffer cb0_; diff --git a/src/detail/brotli_filter_base.hpp b/src/detail/brotli_filter_base.hpp new file mode 100644 index 00000000..f4a96aa4 --- /dev/null +++ b/src/detail/brotli_filter_base.hpp @@ -0,0 +1,49 @@ +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 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/buffers +// + +#ifndef BOOST_HTTP_PROTO_DETAIL_BROTLI_FILTER_BASE_HPP +#define BOOST_HTTP_PROTO_DETAIL_BROTLI_FILTER_BASE_HPP + +#include + +#include "src/detail/filter.hpp" + +namespace boost { +namespace http_proto { +namespace detail { + +/** Base class for brotli filters +*/ +class brotli_filter_base : public filter +{ +protected: + static + void* + alloc(void* opaque, std::size_t size) noexcept + { + return reinterpret_cast(opaque) + ->try_reserve_front(size); + } + + static + void + free( + void* /* opaque */, + void* /* addr */) noexcept + { + // no-op + } +}; + +} // detail +} // http_proto +} // boost + +#endif diff --git a/src/detail/filter.cpp b/src/detail/filter.cpp new file mode 100644 index 00000000..9550f7d7 --- /dev/null +++ b/src/detail/filter.cpp @@ -0,0 +1,82 @@ +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 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 +// + +#include "src/detail/filter.hpp" + +#include +#include +#include + +namespace boost { +namespace http_proto { +namespace detail { + +auto +filter:: +process( + buffers::mutable_buffer_subspan out, + buffers::const_buffer_pair in, + bool more, + bool partial_flush) -> results +{ + results rv; + auto flush = filter::flush::none; + for(;;) + { + if(!more && flush != filter::flush::finish && in[1].size() == 0) + { + if(buffers::size(out) < min_out_buffer()) + { + rv.out_short = true; + return rv; + } + flush = filter::flush::finish; + } + + auto ob = buffers::front(out); + auto ib = buffers::front(in); + auto rs = do_process(ob, ib, flush); + + rv.in_bytes += rs.in_bytes; + rv.out_bytes += rs.out_bytes; + + if(rs.ec.failed()) + { + rv.ec = rs.ec; + return rv; + } + + if(rs.finished) + { + rv.finished = true; + return rv; + } + + out = buffers::sans_prefix(out, rs.out_bytes); + in = buffers::sans_prefix(in, rs.in_bytes); + + if(buffers::size(out) == 0) + return rv; + + if(buffers::size(in) == 0 && rs.out_bytes < ob.size()) + { + if(partial_flush && rv.out_bytes == 0) + { + flush = filter::flush::partial; + continue; + } + return rv; + } + } +} + +} // detail +} // http_proto +} // boost diff --git a/src/detail/zlib_filter.hpp b/src/detail/filter.hpp similarity index 73% rename from src/detail/zlib_filter.hpp rename to src/detail/filter.hpp index 347f5ebc..30bed1c1 100644 --- a/src/detail/zlib_filter.hpp +++ b/src/detail/filter.hpp @@ -5,33 +5,33 @@ // 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/buffers +// Official repository: https://github.com/cppalliance/http_proto // -#ifndef BOOST_HTTP_PROTO_DETAIL_ZLIB_FILTER_HPP -#define BOOST_HTTP_PROTO_DETAIL_ZLIB_FILTER_HPP - -#include +#ifndef BOOST_HTTP_PROTO_DETAIL_FILTER_HPP +#define BOOST_HTTP_PROTO_DETAIL_FILTER_HPP #include #include -#include -#include -#include +#include namespace boost { namespace http_proto { namespace detail { -/** Base class for zlib filters +/** Base class for all filters */ -class zlib_filter +class filter { public: /** The results of processing the filter. */ struct results { + /** The error, if any occurred. + */ + system::error_code ec; + /** The number of bytes produced in the output. This may be less than the total number @@ -48,10 +48,6 @@ class zlib_filter */ std::size_t in_bytes = 0; - /** The error, if any occurred. - */ - system::error_code ec; - /** True if the output buffer is too small to make progress. @@ -64,25 +60,34 @@ class zlib_filter bool finished = false; }; - zlib_filter(workspace& ws); - results process( buffers::mutable_buffer_subspan out, buffers::const_buffer_pair in, bool more, - bool force_flush = false); + bool partial_flush = false); protected: - rts::zlib::stream strm_; + enum class flush + { + none, + partial, + finish + }; virtual std::size_t - min_out_buffer() const noexcept = 0; + min_out_buffer() const noexcept + { + return 0; + } virtual - rts::zlib::error - do_process(rts::zlib::flush) noexcept = 0; + results + do_process( + buffers::mutable_buffer, + buffers::const_buffer, + flush) noexcept = 0; }; } // detail diff --git a/src/detail/header.cpp b/src/detail/header.cpp index d3877ba5..e09c4f4b 100644 --- a/src/detail/header.cpp +++ b/src/detail/header.cpp @@ -9,7 +9,6 @@ // #include -#include #include #include #include @@ -17,6 +16,9 @@ #include #include #include + +#include "../rfc/transfer_encoding_rule.hpp" + #include #include #include @@ -25,10 +27,9 @@ #include #include #include -#include + #include -#include "../rfc/transfer_encoding_rule.hpp" namespace boost { namespace http_proto { @@ -212,11 +213,10 @@ bytes_needed( if(size < 19) size = 19; - return - align_up( - size, - alignof(header::entry)) + - count * sizeof(header::entry); + // align size up to alignof(entry) + size = (size + alignof(entry) - 1) & ~(alignof(entry) - 1); + + return size + count * sizeof(entry); } std::size_t @@ -703,6 +703,12 @@ on_insert_content_encoding( md.content_encoding.encoding = encoding::gzip; } + else if( grammar::ci_is_equal(*(rv->begin()), + "br") ) + { + md.content_encoding.encoding = + encoding::br; + } else { md.content_encoding.encoding = @@ -1164,11 +1170,11 @@ parse_start_line( auto sm = std::get<0>(*rv); h.req.method = string_to_method(sm); h.req.method_len = - static_cast(sm.size()); + static_cast(sm.size()); // target auto st = std::get<1>(*rv); h.req.target_len = - static_cast(st.size()); + static_cast(st.size()); // version switch(std::get<2>(*rv)) { @@ -1225,7 +1231,7 @@ parse_start_line( std::get<1>(*rv).v); h.res.status = std::get<1>(*rv).st; } - h.prefix = static_cast(it - it0); + h.prefix = static_cast(it - it0); h.size = h.prefix; h.on_start_line(); } @@ -1253,7 +1259,7 @@ parse_field( { // final CRLF h.size = static_cast< - offset_type>(it - h.cbuf); + header::offset_type>(it - h.cbuf); return; } if( ec == grammar::error::need_more && @@ -1277,7 +1283,7 @@ parse_field( remove_obs_fold(h.buf + h.size, it); } auto id = string_to_field(rv->name); - h.size = static_cast(it - h.cbuf); + h.size = static_cast(it - h.cbuf); // add field table entry if(h.buf != nullptr) @@ -1286,13 +1292,13 @@ parse_field( h.buf + h.cap)[h.count]; auto const base = h.buf + h.prefix; - e.np = static_cast( + e.np = static_cast( rv->name.data() - base); - e.nn = static_cast( + e.nn = static_cast( rv->name.size()); - e.vp = static_cast( + e.vp = static_cast( rv->value.data() - base); - e.vn = static_cast( + e.vn = static_cast( rv->value.size()); e.id = id; } diff --git a/src/detail/zlib_filter.cpp b/src/detail/zlib_filter.cpp deleted file mode 100644 index ea4e9e4f..00000000 --- a/src/detail/zlib_filter.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) -// Copyright (c) 2024 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/buffers -// - -#include "src/detail/zlib_filter.hpp" - -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -namespace { - -void* -zalloc( - void* opaque, - unsigned items, - unsigned size) noexcept -{ - return reinterpret_cast(opaque) - ->try_reserve_front(items * size); -} - -void -zfree( - void* /* opaque */, - void* /* addr */) noexcept -{ - // no-op -} - -unsigned int -saturate_cast(std::size_t n) noexcept -{ - if(n >= std::numeric_limits::max()) - return std::numeric_limits::max(); - return static_cast(n); -} - -} // namespace - -zlib_filter:: -zlib_filter(workspace& ws) -{ - strm_.zalloc = &zalloc; - strm_.zfree = &zfree; - strm_.opaque = &ws; -} - -auto -zlib_filter:: -process( - buffers::mutable_buffer_subspan out, - buffers::const_buffer_pair in, - bool more, - bool force_flush) -> results -{ - results rv; - auto flush = rts::zlib::no_flush; - for(;;) - { - auto ob = buffers::front(out); - auto ib = buffers::front(in); - - if(!more && flush != rts::zlib::finish && in[1].size() == 0) - { - if(buffers::size(out) < min_out_buffer()) - { - rv.out_short = true; - return rv; - } - flush = rts::zlib::finish; - } - - strm_.next_in = static_cast(const_cast(ib.data())); - strm_.avail_in = saturate_cast(ib.size()); - strm_.next_out = static_cast(ob.data()); - strm_.avail_out = saturate_cast(ob.size()); - - auto ec = BOOST_HTTP_PROTO_ERR( - do_process(flush)); - - const std::size_t in_bytes = saturate_cast(ib.size()) - strm_.avail_in; - const std::size_t out_bytes = saturate_cast(ob.size()) - strm_.avail_out; - - rv.in_bytes += in_bytes; - rv.out_bytes += out_bytes; - - if(ec.failed()) - return rv; - - if(ec == rts::zlib::error::stream_end) - { - rv.finished = true; - return rv; - } - - out = buffers::sans_prefix(out, out_bytes); - in = buffers::sans_prefix(in, in_bytes); - - if(buffers::size(out) == 0) - return rv; - - if(buffers::size(in) == 0 && strm_.avail_out != 0) - { - if(force_flush && rv.out_bytes == 0) - { - flush = rts::zlib::block; - continue; - } - return rv; - } - } -} - -} // detail -} // http_proto -} // boost diff --git a/src/detail/zlib_filter_base.hpp b/src/detail/zlib_filter_base.hpp new file mode 100644 index 00000000..37156212 --- /dev/null +++ b/src/detail/zlib_filter_base.hpp @@ -0,0 +1,93 @@ +// +// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 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/buffers +// + +#ifndef BOOST_HTTP_PROTO_DETAIL_ZLIB_FILTER_BASE_HPP +#define BOOST_HTTP_PROTO_DETAIL_ZLIB_FILTER_BASE_HPP + +#include + +#include "src/detail/filter.hpp" + +#include +#include + +namespace boost { +namespace http_proto { +namespace detail { + +/** Base class for zlib filters +*/ +class zlib_filter_base : public filter +{ +public: + zlib_filter_base(workspace& w) + { + strm_.zalloc = &zalloc; + strm_.zfree = &zfree; + strm_.opaque = &w; + } + +protected: + rts::zlib::stream strm_; + + static + rts::zlib::flush + translate(filter::flush flush) noexcept + { + switch(flush) + { + case filter::flush::none: + return rts::zlib::flush::no_flush; + + case filter::flush::partial: + return rts::zlib::flush::block; + + case filter::flush::finish: + default: + return rts::zlib::flush::finish; + } + } + + static + unsigned int + saturate_cast(std::size_t n) noexcept + { + if(n >= std::numeric_limits::max()) + return std::numeric_limits::max(); + return static_cast(n); + } + +private: + static + void* + zalloc( + void* opaque, + unsigned items, + unsigned size) noexcept + { + return reinterpret_cast(opaque) + ->try_reserve_front(items * size); + } + + static + void + zfree( + void* /* opaque */, + void* /* addr */) noexcept + { + // no-op + } +}; + +} // detail +} // http_proto +} // boost + +#endif diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 5ab53732..1804d2a9 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -8,34 +8,28 @@ // Official repository: https://github.com/cppalliance/http_proto // -#include - +#include +#include +#include #include #include +#include #include #include #include -#include -#include -#include -#include +#include "src/detail/move_chars.hpp" +#include "src/rfc/detail/rules.hpp" #include #include - #include - #include - #include #include #include #include -#include "src/detail/move_chars.hpp" -#include "src/rfc/detail/rules.hpp" - namespace boost { namespace http_proto { @@ -151,14 +145,8 @@ class fields_base:: return table(end()); } - static - std::size_t - growth( - std::size_t n0, - std::size_t m) noexcept; - bool - reserve(std::size_t bytes); + reserve(std::size_t n); bool grow( @@ -177,43 +165,12 @@ class fields_base:: std::size_t n) const noexcept; }; -/* Growth functions for containers - - N1 = g( N0, M ); - - g = growth function - M = minimum capacity - N0 = old size - N1 = new size -*/ -std::size_t -fields_base:: -op_t:: -growth( - std::size_t n0, - std::size_t m) noexcept -{ - auto const m1 = - detail::align_up(m, alignof(entry)); - BOOST_ASSERT(m1 >= m); - if(n0 == 0) - { - // exact - return m1; - } - if(m1 > n0) - return m1; - return n0; -} - bool fields_base:: op_t:: reserve( - std::size_t bytes) + std::size_t n) { - auto n = growth( - self_.h_.cap, bytes); if(n > self_.max_capacity_in_bytes()) { // max capacity exceeded @@ -238,24 +195,16 @@ grow( std::size_t extra_char, std::size_t extra_field) { - // extra_field is naturally limited - // by max_offset, since each field - // is at least 4 bytes: "X:\r\n" - BOOST_ASSERT( - extra_field <= max_offset && - extra_field <= static_cast< - std::size_t>( - max_offset - self_.h_.count)); - if( extra_char > max_offset || - extra_char > static_cast( - max_offset - self_.h_.size)) + if(extra_field > detail::header::max_offset - self_.h_.count) detail::throw_length_error(); - auto n1 = growth( - self_.h_.cap, + + if(extra_char > detail::header::max_offset - self_.h_.size) + detail::throw_length_error(); + + return reserve( detail::header::bytes_needed( self_.h_.size + extra_char, self_.h_.count + extra_field)); - return reserve(n1); } void @@ -305,7 +254,7 @@ prefix_op_t( offset_type>(new_prefix)) { if(self.h_.size - self.h_.prefix + new_prefix - > max_offset) + > detail::header::max_offset) detail::throw_length_error(); // memmove happens in the destructor @@ -314,8 +263,9 @@ prefix_op_t( && !self.h_.is_default()) return; - auto new_size = - self.h_.size - self.h_.prefix + new_prefix_; + auto new_size = static_cast( + self.h_.size - self.h_.prefix + new_prefix_); + if(new_size > self.h_.cap) { // static storage will always throw which is @@ -414,11 +364,10 @@ fields_base( : fields_view_base(&h_) , h_(k) { - if( storage_size > 0 ) + if(storage_size != 0) { - h_.max_cap = detail::align_up( - storage_size, alignof(detail::header::entry)); reserve_bytes(storage_size); + h_.max_cap = h_.cap; } } @@ -430,18 +379,11 @@ fields_base( : fields_view_base(&h_) , h_(k) { - if( storage_size > max_storage_size ) + if(storage_size > max_storage_size) detail::throw_length_error(); - if( max_storage_size > h_.max_capacity_in_bytes() ) - detail::throw_length_error(); - - h_.max_cap = detail::align_up( - max_storage_size, alignof(detail::header::entry)); - if( storage_size > 0 ) - { - reserve_bytes(storage_size); - } + reserve_bytes(storage_size); + h_.max_cap = max_storage_size; } // copy s and parse it diff --git a/src/parser.cpp b/src/parser.cpp index 53044583..7b7f6460 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -13,7 +13,8 @@ #include #include -#include "src/detail/zlib_filter.hpp" +#include "src/detail/brotli_filter_base.hpp" +#include "src/detail/zlib_filter_base.hpp" #include #include @@ -21,7 +22,9 @@ #include #include #include -#include +#include +#include +#include #include #include @@ -293,19 +296,18 @@ clamp( return limit; return static_cast(x); } -} // namespace -class parser::filter - : public detail::zlib_filter +class zlib_filter + : public detail::zlib_filter_base { rts::zlib::inflate_service& svc_; public: - filter( + zlib_filter( rts::context& ctx, http_proto::detail::workspace& ws, int window_bits) - : zlib_filter(ws) + : zlib_filter_base(ws) , svc_(ctx.get_service()) { system::error_code ec = static_cast( @@ -316,21 +318,95 @@ class parser::filter private: virtual - std::size_t - min_out_buffer() const noexcept override + results + do_process( + buffers::mutable_buffer out, + buffers::const_buffer in, + filter::flush flush) noexcept override + { + strm_.next_out = static_cast(out.data()); + strm_.avail_out = saturate_cast(out.size()); + strm_.next_in = static_cast(const_cast(in.data())); + strm_.avail_in = saturate_cast(in.size()); + + auto rs = static_cast( + svc_.inflate(strm_, translate(flush))); + + results rv; + rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out; + rv.in_bytes = saturate_cast(in.size()) - strm_.avail_in; + rv.finished = (rs == rts::zlib::error::stream_end); + + if(rs < rts::zlib::error::ok && rs != rts::zlib::error::buf_err) + rv.ec = rs; + + return rv; + } +}; + +class brotli_filter + : public detail::brotli_filter_base +{ + rts::brotli::decode_service& svc_; + rts::brotli::decoder_state* state_; + +public: + brotli_filter( + rts::context& ctx, + http_proto::detail::workspace&) + : svc_(ctx.get_service()) + { + // TODO: use custom allocator + state_ = svc_.create_instance(nullptr, nullptr, nullptr); + + if(!state_) + detail::throw_bad_alloc(); + } + + ~brotli_filter() { - return 0; + svc_.destroy_instance(state_); } +private: virtual - rts::zlib::error - do_process(rts::zlib::flush flush) noexcept override + results + do_process( + buffers::mutable_buffer out, + buffers::const_buffer in, + filter::flush flush) noexcept override { - return static_cast< - rts::zlib::error>(svc_.inflate(strm_, flush)); + auto* next_in = reinterpret_cast(in.data()); + auto available_in = in.size(); + auto* next_out = reinterpret_cast(out.data()); + auto available_out = out.size(); + + auto rs = svc_.decompress_stream( + state_, + &available_in, + &next_in, + &available_out, + &next_out, + nullptr); + + results rv; + rv.in_bytes = in.size() - available_in; + rv.out_bytes = out.size() - available_out; + rv.finished = svc_.is_finished(state_); + + if(rs == rts::brotli::decoder_result::needs_more_input && flush == filter::flush::finish) + rv.ec = BOOST_HTTP_PROTO_ERR(error::bad_payload); + + if(rs == rts::brotli::decoder_result::error) + rv.ec = BOOST_HTTP_PROTO_ERR( + svc_.get_error_code(state_)); + + return rv; } }; +} // namespace + class parser_service : public rts::service { @@ -396,8 +472,8 @@ class parser_service #ifdef __s390x__ 5768 + #endif - detail::workspace:: - space_needed(); + detail::workspace::space_needed< + zlib_filter>(); if(max_codec < n) max_codec = n; @@ -1009,22 +1085,34 @@ parse( // must be installed after them. auto const p = ws_.reserve_front(cap); - if(svc_.cfg.apply_deflate_decoder && - h_.md.content_encoding.encoding == encoding::deflate) + switch(h_.md.content_encoding.encoding) { - filter_ = &ws_.emplace( + case encoding::deflate: + if(!svc_.cfg.apply_deflate_decoder) + goto no_filter; + filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_window_bits); - } - else if(svc_.cfg.apply_gzip_decoder && - h_.md.content_encoding.encoding == encoding::gzip) - { - filter_ = &ws_.emplace( + break; + + case encoding::gzip: + if(!svc_.cfg.apply_deflate_decoder) + goto no_filter; + filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_window_bits + 16); - } - else - { + break; + + case encoding::br: + if(!svc_.cfg.apply_brotli_decoder) + goto no_filter; + filter_ = &ws_.emplace( + ctx_, ws_); + break; + + no_filter: + default: cap += svc_.max_codec; ws_.reserve_front(svc_.max_codec); + break; } if(is_plain() || how_ == how::elastic) @@ -1610,9 +1698,6 @@ apply_filter( payload_avail -= f_rs.in_bytes; body_total_ += f_rs.out_bytes; - bool needs_more_space = !f_rs.finished && - payload_avail != 0; - switch(how_) { case how::in_place: @@ -1620,7 +1705,7 @@ apply_filter( cb1_.commit(f_rs.out_bytes); body_avail_ += f_rs.out_bytes; if(cb1_.capacity() == 0 && - needs_more_space) + !f_rs.finished && f_rs.in_bytes == 0) { ec = BOOST_HTTP_PROTO_ERR( error::in_place_overflow); @@ -1646,7 +1731,7 @@ apply_filter( { eb_->commit(f_rs.out_bytes); if(eb_->max_size() - eb_->size() == 0 && - needs_more_space) + !f_rs.finished && f_rs.in_bytes == 0) { ec = BOOST_HTTP_PROTO_ERR( error::buffer_overflow); @@ -1665,7 +1750,7 @@ apply_filter( } if(body_limit_remain() == 0 && - needs_more_space) + !f_rs.finished && f_rs.in_bytes == 0) { ec = BOOST_HTTP_PROTO_ERR( error::body_too_large); diff --git a/src/serializer.cpp b/src/serializer.cpp index 8fa56310..d8b3de4f 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -13,7 +13,8 @@ #include #include -#include "src/detail/zlib_filter.hpp" +#include "src/detail/brotli_filter_base.hpp" +#include "src/detail/zlib_filter_base.hpp" #include #include @@ -22,9 +23,12 @@ #include #include #include -#include +#include #include #include +#include +#include +#include #include @@ -183,23 +187,19 @@ class appender } }; -} // namespace - -//------------------------------------------------ - -class serializer::filter - : public detail::zlib_filter +class zlib_filter + : public detail::zlib_filter_base { rts::zlib::deflate_service& svc_; public: - filter( + zlib_filter( rts::context& ctx, http_proto::detail::workspace& ws, int comp_level, int window_bits, int mem_level) - : zlib_filter(ws) + : zlib_filter_base(ws) , svc_(ctx.get_service()) { system::error_code ec = static_cast(svc_.init2( @@ -224,14 +224,134 @@ class serializer::filter } virtual - rts::zlib::error - do_process(rts::zlib::flush flush) noexcept override + results + do_process( + buffers::mutable_buffer out, + buffers::const_buffer in, + filter::flush flush) noexcept override { - return static_cast< - rts::zlib::error>(svc_.deflate(strm_, flush)); + strm_.next_out = static_cast(out.data()); + strm_.avail_out = saturate_cast(out.size()); + strm_.next_in = static_cast(const_cast(in.data())); + strm_.avail_in = saturate_cast(in.size()); + + auto rs = static_cast( + svc_.deflate(strm_, translate(flush))); + + results rv; + rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out; + rv.in_bytes = saturate_cast(in.size()) - strm_.avail_in; + rv.finished = (rs == rts::zlib::error::stream_end); + + if(rs < rts::zlib::error::ok && rs != rts::zlib::error::buf_err) + rv.ec = rs; + + return rv; } }; +class brotli_filter + : public detail::brotli_filter_base +{ + rts::brotli::encode_service& svc_; + rts::brotli::encoder_state* state_; + bool flushing_ = false; +public: + brotli_filter( + rts::context& ctx, + http_proto::detail::workspace&, + std::uint32_t comp_quality, + std::uint32_t comp_window) + : svc_(ctx.get_service()) + { + // TODO: use custom allocator + state_ = svc_.create_instance(nullptr, nullptr, nullptr); + if(!state_) + detail::throw_bad_alloc(); + using encoder_parameter = rts::brotli::encoder_parameter; + svc_.set_parameter(state_, encoder_parameter::quality, comp_quality); + svc_.set_parameter(state_, encoder_parameter::lgwin, comp_window); + } + + ~brotli_filter() + { + svc_.destroy_instance(state_); + } + +private: + virtual + results + do_process( + buffers::mutable_buffer out, + buffers::const_buffer in, + filter::flush flush) noexcept override + { + if(flushing_) + { + // restore partial flush type + flush = filter::flush::partial; + in = {}; + } + else if(flush == filter::flush::partial) + { + // the Brotli api requires continued flushing + // until all output is fully drained. + flushing_ = true; + } + + auto* next_in = reinterpret_cast(in.data()); + auto available_in = in.size(); + auto* next_out = reinterpret_cast(out.data()); + auto available_out = out.size(); + + bool rs = svc_.compress_stream( + state_, + translate(flush), + &available_in, + &next_in, + &available_out, + &next_out, + nullptr); + + results rv; + rv.in_bytes = in.size() - available_in; + rv.out_bytes = out.size() - available_out; + rv.finished = svc_.is_finished(state_); + + if(flushing_ && !svc_.has_more_output(state_)) + flushing_ = false; + + // TODO: use proper error code + if(rs == false) + rv.ec = error::bad_payload; + + return rv; + } + +private: + static + rts::brotli::encoder_operation + translate(filter::flush flush) noexcept + { + switch(flush) + { + case filter::flush::none: + return rts::brotli::encoder_operation::process; + + case filter::flush::partial: + return rts::brotli::encoder_operation::flush; + + case filter::flush::finish: + default: + return rts::brotli::encoder_operation::finish; + } + } +}; + +} // namespace + +//------------------------------------------------ + class serializer_service : public rts::service { @@ -260,8 +380,7 @@ class serializer_service #ifdef __s390x__ 5768 + #endif - detail::workspace::space_needed< - serializer::filter>(); + detail::workspace::space_needed(); } } }; @@ -275,6 +394,8 @@ install_serializer_service( serializer_service>(cfg); } +//------------------------------------------------ + serializer:: ~serializer() { @@ -620,24 +741,43 @@ start_init( is_chunked_ = md.transfer_encoding.is_chunked; // Content-Encoding - auto const& ce = md.content_encoding; - if(ce.encoding == encoding::deflate) + switch (md.content_encoding.encoding) { - filter_ = &ws_.emplace( + case encoding::deflate: + if(!svc_.cfg.apply_deflate_encoder) + goto no_filter; + filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_comp_level, svc_.cfg.zlib_window_bits, svc_.cfg.zlib_mem_level); - } - else if(ce.encoding == encoding::gzip) - { - filter_ = &ws_.emplace( + break; + + case encoding::gzip: + if(!svc_.cfg.apply_gzip_encoder) + goto no_filter; + filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_comp_level, svc_.cfg.zlib_window_bits + 16, svc_.cfg.zlib_mem_level); + break; + + case encoding::br: + if(!svc_.cfg.apply_brotli_encoder) + goto no_filter; + filter_ = &ws_.emplace( + ctx_, + ws_, + svc_.cfg.brotli_comp_quality, + svc_.cfg.brotli_comp_window); + break; + + no_filter: + default: + break; } } diff --git a/test/limits/CMakeLists.txt b/test/limits/CMakeLists.txt index 6133e2b9..179d2d9a 100644 --- a/test/limits/CMakeLists.txt +++ b/test/limits/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2019 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) @@ -17,8 +18,8 @@ add_executable(boost_http_proto_limits limits.cpp Jamfile ${TEST_MAIN} ${LIMITS_ target_include_directories(boost_http_proto_limits PRIVATE ../../) target_include_directories(boost_http_proto_limits PRIVATE ../../include ../../../url/extra/test_suite ../../..) target_compile_definitions(boost_http_proto_limits PRIVATE - BOOST_HTTP_PROTO_MAX_HEADER=20 - BOOST_HTTP_PROTO_NO_LIB=1 + BOOST_HTTP_PROTO_TEST_FORCE_8BIT_OFFSET + BOOST_HTTP_PROTO_NO_LIB BOOST_HTTP_PROTO_STATIC_LINK ) target_link_libraries(boost_http_proto_limits PRIVATE diff --git a/test/limits/Jamfile b/test/limits/Jamfile index 35ada0a7..b1cb798f 100644 --- a/test/limits/Jamfile +++ b/test/limits/Jamfile @@ -1,5 +1,6 @@ # # Copyright (c) 2019 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) @@ -24,7 +25,7 @@ project run limits.cpp /boost/http_proto//http_proto_sources : requirements - BOOST_HTTP_PROTO_MAX_HEADER=20 + BOOST_HTTP_PROTO_TEST_FORCE_8BIT_OFFSET BOOST_HTTP_PROTO_NO_LIB BOOST_HTTP_PROTO_STATIC_LINK static diff --git a/test/limits/limits.cpp b/test/limits/limits.cpp index 80d7fa42..b870205c 100644 --- a/test/limits/limits.cpp +++ b/test/limits/limits.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2019 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) @@ -10,6 +11,7 @@ #include #include #include +#include #include "test_suite.hpp" @@ -23,82 +25,118 @@ #error "Limits should not be built with shared linking." #endif +#ifndef BOOST_HTTP_PROTO_TEST_FORCE_8BIT_OFFSET +#error "Limits should be built with BOOST_HTTP_PROTO_TEST_FORCE_8BIT_OFFSET." +#endif + namespace boost { namespace http_proto { -// This has to be at least 19 because -// of the default request buffer. -static_assert(max_offset == 20, "max_offset != 20"); +constexpr auto max_offset = + std::numeric_limits::max(); class limits_test { + static + std::string + make_str(std::size_t n) + { + return std::string(n, '*'); + } + public: void testLimits() { + // request { request req; + core::string_view const cs = + "POST HTTP/1.1\r\n" + "\r\n"; + const auto remain = max_offset - cs.size(); + BOOST_TEST_THROWS( req.set_start_line( - method::connect, - "https://www.example.com", + method::post, + make_str(remain + 1), version::http_1_1), std::length_error); + + req.set_start_line( + method::post, + make_str(remain), + version::http_1_1); + + BOOST_TEST_EQ( + req.buffer().size(), max_offset); + } + + // response + { + response res; + core::string_view const cs = + "HTTP/1.1 200 \r\n" + "\r\n"; + const auto remain = max_offset - cs.size(); + + BOOST_TEST_THROWS( + res.set_start_line( + 200, + make_str(remain + 1), + version::http_1_1), + std::length_error); + + res.set_start_line( + 200, + make_str(remain), + version::http_1_1); + + BOOST_TEST_EQ( + res.buffer().size(), max_offset); } } void testFields() { - // reserve - { - { - fields f; - BOOST_TEST_NO_THROW( - f.reserve_bytes(0)); - } - { - fields f; - BOOST_TEST_NO_THROW( - f.reserve_bytes(max_offset)); - } - { - fields f; - BOOST_TEST_NO_THROW(f.reserve_bytes( - f.max_capacity_in_bytes())); - } - { - fields f; - BOOST_TEST_THROWS( - f.reserve_bytes(max_offset + 1), - std::length_error); - } - { - fields f; - BOOST_TEST_THROWS(f.reserve_bytes( - f.max_capacity_in_bytes() + 1), - std::length_error); - } - } + core::string_view const cs = + ": \r\n" + "\r\n"; + const auto remain = max_offset - cs.size(); // append { fields f; - std::string s; - s.append(max_offset, 'x'); BOOST_TEST_THROWS( - f.append(s, "v"), + f.append( + make_str(remain / 2 + 1), + make_str(remain / 2 + 1)), std::length_error); + + f.append( + make_str(remain / 2), + make_str(remain / 2 + 1)); + + BOOST_TEST_EQ( + f.buffer().size(), max_offset); } // set { fields f; BOOST_TEST_THROWS( - f.set("x", - "0123456789" - "0123456789"), + f.set( + make_str(remain / 2 + 1), + make_str(remain / 2 + 1)), std::length_error); + + f.set( + make_str(remain / 2), + make_str(remain / 2 + 1)); + + BOOST_TEST_EQ( + f.buffer().size(), max_offset); } } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b8ed5952..2847b837 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -35,5 +35,9 @@ if (TARGET boost_rts_zlib) target_link_libraries(boost_http_proto_tests PRIVATE boost_rts_zlib) endif () +if (TARGET boost_rts_brotli) + target_link_libraries(boost_http_proto_tests PRIVATE boost_rts_brotli) +endif () + add_test(NAME boost_http_proto_tests COMMAND boost_http_proto_tests) add_dependencies(tests boost_http_proto_tests) diff --git a/test/unit/Jamfile b/test/unit/Jamfile index 614e27f8..5fe55142 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -16,7 +16,9 @@ project : requirements $(c11-requires) /boost/http_proto//boost_http_proto + /boost/url//boost_url [ ac.check-library /boost/rts//boost_rts_zlib : /boost/rts//boost_rts_zlib : ] + [ ac.check-library /boost/rts//boost_rts_brotli : /boost/rts//boost_rts_brotli : ] ../../../url/extra/test_suite/test_main.cpp ../../../url/extra/test_suite/test_suite.cpp ./test_helpers.cpp @@ -28,6 +30,7 @@ project ; local SOURCES = + compression.cpp() error.cpp field.cpp fields.cpp @@ -61,7 +64,6 @@ local SOURCES = string_body.cpp test_helpers.cpp version.cpp - zlib.cpp rfc/combine_field_values.cpp rfc/list_rule.cpp rfc/parameter.cpp diff --git a/test/unit/compression.cpp b/test/unit/compression.cpp new file mode 100644 index 00000000..edc87deb --- /dev/null +++ b/test/unit/compression.cpp @@ -0,0 +1,700 @@ +// +// Copyright (c) 2024 Christian Mazakas +// Copyright (c) 2024 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 +// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "test_helpers.hpp" + +#include +#include +#include + +namespace boost { +namespace http_proto { + +struct zlib_test +{ + static + std::string + make_rand_string(std::size_t length) + { + const char chars[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, sizeof(chars) - 2); + + std::string result; + result.resize(length); + std::generate( + result.begin(), + result.end(), + [&]() { return chars[dist(rng)]; }); + + return result; + } + + static + std::string + compress( + const rts::context& ctx, + core::string_view encoding, + core::string_view body) + { + std::string result; + auto buf = buffers::string_buffer(&result); + + if(encoding == "deflate" || encoding == "gzip") + { + namespace zlib = rts::zlib; + auto& svc = ctx.get_service(); + zlib::stream zs{}; + + auto ret = static_cast( + svc.init2( + zs, + zlib::default_compression, + zlib::deflated, + encoding == "deflate" ? 15 : 31, + 8, + zlib::default_strategy)); + + if(!BOOST_TEST_EQ(ret, zlib::error::ok)) + { + svc.deflate_end(zs); + return {}; + } + + zs.next_in = reinterpret_cast(const_cast(body.data())); + zs.avail_in = static_cast(body.size()); + + for(;;) + { + zs.next_out = reinterpret_cast(buf.prepare(64 * 1024).data()); + zs.avail_out = 64 * 1024; + ret = static_cast(svc.deflate(zs, zlib::finish)); + buf.commit(64 * 1024 - zs.avail_out); + if(ret != zlib::error::ok) + break; + } + + svc.deflate_end(zs); + } + else if(encoding == "br") + { + namespace brotli = rts::brotli; + auto& svc = ctx.get_service(); + + brotli::encoder_state* state = + svc.create_instance(nullptr, nullptr, nullptr); + + if(!BOOST_TEST_NE(state, nullptr)) + return {}; + + auto* next_in = reinterpret_cast(body.data()); + auto available_in = body.size(); + + svc.set_parameter( + state, brotli::encoder_parameter::quality, 5); + do + { + auto* next_out = reinterpret_cast(buf.prepare(64 * 1024).data()); + std::size_t available_out = 64 * 1024; + auto ret = svc.compress_stream( + state, + brotli::encoder_operation::finish, + &available_in, + &next_in, + &available_out, + &next_out, + nullptr); + buf.commit(64 * 1024 - available_out); + if(!BOOST_TEST_EQ(ret, true)) + break; + } while(!svc.is_finished(state)); + + svc.destroy_instance(state); + } + else + { + BOOST_TEST_FAIL(); + } + + result.resize(buf.size()); + return result; + } + + static + void + verify_compressed( + const rts::context& ctx, + core::string_view encoding, + core::string_view compressed_body, + core::string_view body) + { + if(encoding == "deflate" || encoding == "gzip") + { + namespace zlib = rts::zlib; + auto& svc = ctx.get_service(); + zlib::stream zs{}; + + auto ret = static_cast( + svc.init2( + zs, + encoding == "deflate" ? 15 : 31)); + if(!BOOST_TEST_EQ(ret, zlib::error::ok)) + { + svc.inflate_end(zs); + return; + } + + zs.next_in = reinterpret_cast(const_cast(compressed_body.data())); + zs.avail_in = static_cast(compressed_body.size()); + + for(;;) + { + char buf[64 * 1024]; + zs.next_out = reinterpret_cast(&buf[0]); + zs.avail_out = 64 * 1024; + ret = static_cast(svc.inflate(zs, zlib::finish)); + auto piece = core::string_view{ &buf[0], 64 * 1024 - zs.avail_out }; + if(!BOOST_TEST(body.starts_with(piece))) + break; + body.remove_prefix(piece.size()); + if(ret == zlib::error::stream_end) + break; + if(ret == zlib::error::buf_err) + continue; + if(!BOOST_TEST_GE(static_cast(ret), 0)) + break; + }; + + svc.inflate_end(zs); + } + else if(encoding == "br") + { + namespace brotli = rts::brotli; + auto& svc = ctx.get_service(); + + brotli::decoder_state* state = + svc.create_instance(nullptr, nullptr, nullptr); + + if(!BOOST_TEST_NE(state, nullptr)) + return; + + auto* next_in = reinterpret_cast(compressed_body.data()); + auto available_in = compressed_body.size(); + + for(;;) + { + char buf[64 * 1024]; + auto* next_out =reinterpret_cast(&buf[0]); + std::size_t available_out = 64 * 1024; + brotli::decoder_result ret = svc.decompress_stream( + state, + &available_in, + &next_in, + &available_out, + &next_out, + nullptr); + auto piece = core::string_view{ &buf[0], 64 * 1024 - available_out }; + if(!BOOST_TEST(body.starts_with(piece))) + break; + body.remove_prefix(piece.size()); + if(svc.is_finished(state)) + break; + if(!BOOST_TEST_NE(ret, brotli::decoder_result::error)) + break; + }; + + svc.destroy_instance(state); + } + else + { + BOOST_TEST_FAIL(); + } + + BOOST_TEST(body.empty()); + } + + static + void + serializer_source( + response_view res, + serializer& sr, + buffers::const_buffer body, + buffers::string_buffer out) + { + class source_t : public source + { + buffers::const_buffer body_; + + public: + source_t(buffers::const_buffer body) + : body_(body) + { + } + + results + on_read(buffers::mutable_buffer b) + { + results rs; + auto n = buffers::copy(b, body_); + body_ = buffers::sans_prefix(body_, n); + rs.bytes = n; + rs.ec = {}; + rs.finished = (body_.size() == 0); + return rs; + } + }; + + + sr.start(res, body); + + do + { + auto cbs = sr.prepare().value(); + BOOST_TEST_GT(buffers::size(cbs), 0); + auto n = buffers::copy( + out.prepare(buffers::size(cbs)), cbs); + BOOST_TEST_EQ(n, buffers::size(cbs)); + out.commit(n); + sr.consume(n); + } while(!sr.is_done()); + } + + static + void + serializer_stream( + response_view res, + serializer& sr, + buffers::const_buffer body, + buffers::string_buffer out) + { + auto stream = sr.start_stream(res); + + int stream_closed = 0; + do + { + if(stream_closed == 0) + { + auto mbs = stream.prepare(); + auto n1 = buffers::copy(mbs, body); + body = buffers::sans_prefix(body, n1); + stream.commit(n1); + if(body.size() == 0) + stream_closed = 1; + } + + auto cbs = sr.prepare().value(); + BOOST_TEST_GT(buffers::size(cbs), 0); + auto n2 = buffers::copy(out.prepare(buffers::size(cbs)), cbs); + BOOST_TEST_EQ(n2, buffers::size(cbs)); + sr.consume(n2); + out.commit(n2); + + if(stream_closed == 1) + { + stream_closed = 2; + stream.close(); + } + + } while(!sr.is_done()); + } + + static + void + serializer_buffers( + response_view res, + serializer& sr, + buffers::const_buffer body, + buffers::string_buffer out) + { + std::vector buf_seq; + + do + { + auto buf_size = std::min(body.size() / 23, body.size()); + if(buf_size == 0) + buf_size = 1; + buf_seq.push_back(buffers::prefix(body, buf_size)); + body = buffers::sans_prefix(body, buf_size); + } while(body.size() != 0); + + sr.start(res, buf_seq); + + do + { + auto cbs = sr.prepare().value(); + BOOST_TEST_GT(buffers::size(cbs), 0); + auto n = buffers::copy( + out.prepare(buffers::size(cbs)), cbs); + BOOST_TEST_EQ(n, buffers::size(cbs)); + out.commit(n); + sr.consume(n); + }while(!sr.is_done()); + } + + void + test_serializer() + { + rts::context ctx; + std::vector encodings; + serializer::config cfg; + + #ifdef BOOST_RTS_HAS_ZLIB + cfg.apply_deflate_encoder = true; + cfg.apply_gzip_encoder = true; + rts::zlib::install_deflate_service(ctx); + rts::zlib::install_inflate_service(ctx); + encodings.push_back("gzip"); + encodings.push_back("deflate"); + #endif + #ifdef BOOST_RTS_HAS_BROTLI + cfg.apply_brotli_encoder = true; + rts::brotli::install_encode_service(ctx); + rts::brotli::install_decode_service(ctx); + encodings.push_back("br"); + #endif + + install_serializer_service(ctx, cfg); + serializer sr(ctx); + sr.reset(); + + const auto rand_string = make_rand_string(1024 * 1024); + + for(core::string_view encoding : encodings) + for(auto chunked : { true, false }) + for(auto body_size : { 0, 7, 64 * 1024, 1024 * 1024 }) + for(auto driver : { serializer_buffers, serializer_stream, serializer_source }) + { + response resp; + resp.set(field::content_encoding, encoding); + if(chunked) + resp.set_chunked(true); + else + resp.set_content_length(body_size); + + auto body = core::string_view{ rand_string }.substr(0, body_size); + std::string buf; + driver( + resp, + sr, + buffers::const_buffer(body.data(), body.size()), + buffers::string_buffer(&buf)); + + BOOST_TEST( + core::string_view{ buf }.starts_with(resp.buffer())); + + auto raw_body = core::string_view{ buf }.substr(resp.buffer().size()); + std::string compressed_body; + + while(!raw_body.empty()) + { + if(!resp.chunked()) + { + compressed_body = raw_body; + break; + } + + auto pos = raw_body.find_first_of("\r\n"); + BOOST_TEST_NE(pos, core::string_view::npos); + + std::string chunk_header = raw_body.substr(0, pos); + raw_body.remove_prefix(pos + 2); + + auto chunk_size = std::stoul(chunk_header, nullptr, 16); + + if(chunk_size == 0) + { + BOOST_TEST(raw_body == "\r\n"); + } + else + { + BOOST_TEST_LT( + raw_body.begin() + chunk_size, + raw_body.end()); + + compressed_body.insert( + compressed_body.end(), + raw_body.data(), + raw_body.data() + chunk_size); + + raw_body.remove_prefix(chunk_size); + BOOST_TEST(raw_body.starts_with("\r\n")); + } + raw_body.remove_prefix(2); + } + + verify_compressed(ctx, encoding, compressed_body, body); + } + } + + static + std::string + parser_pull_body( + response_parser& pr, + buffers::const_buffer input) + { + std::string rs; + buffers::string_buffer buf(&rs); + for(;;) + { + if(input.size() != 0) + { + auto n1 = buffers::copy(pr.prepare(), input); + pr.commit(n1); + input = buffers::sans_prefix(input, n1); + } + + boost::system::error_code ec; + pr.parse(ec); + if(!ec) + pr.parse(ec); + if(ec) + BOOST_TEST(ec == error::in_place_overflow + || ec == error::need_data); + + // consume in_place body + auto n2 = buffers::copy( + buf.prepare(buffers::size(pr.pull_body())), + pr.pull_body()); + buf.commit(n2); + pr.consume_body(n2); + + if( input.size() == 0 && ec == error::need_data ) + { + pr.commit_eof(); + pr.parse(ec); + BOOST_TEST(!ec || ec == error::in_place_overflow); + } + if( pr.is_complete() ) + break; + } + return rs; + } + + static + std::string + parser_elastic_body( + response_parser& pr, + buffers::const_buffer input) + { + std::string rs; + std::size_t n1 = buffers::copy( + pr.prepare(), input); + input = buffers::sans_prefix(input, n1); + pr.commit(n1); + system::error_code ec; + pr.parse(ec); + BOOST_TEST(pr.got_header()); + + buffers::string_buffer buf(&rs); + pr.set_body(std::ref(buf)); + pr.parse(ec); + + while(ec == error::need_data) + { + std::size_t n2 = buffers::copy( + pr.prepare(), input); + input = buffers::sans_prefix(input, n2); + pr.commit(n2); + pr.parse(ec); + if(n2 == 0) + { + pr.commit_eof(); + pr.parse(ec); + break; + } + } + return rs; + } + + static + std::string + parser_sink_body( + response_parser& pr, + buffers::const_buffer input) + { + std::size_t n1 = buffers::copy( + pr.prepare(), input); + input = buffers::sans_prefix(input, n1); + pr.commit(n1); + system::error_code ec; + pr.parse(ec); + BOOST_TEST(pr.got_header()); + + class sink_t : public sink + { + std::string body_; + + public: + std::string + get_body() + { + return body_; + } + + results + on_write( + buffers::const_buffer b, + bool) override + { + body_.append( + static_cast(b.data()), + b.size()); + results rv; + rv.bytes = b.size(); + return rv; + } + }; + + auto& sink = pr.set_body(); + pr.parse(ec); + + while(ec == error::need_data) + { + std::size_t n2 = buffers::copy( + pr.prepare(), input); + input = buffers::sans_prefix(input, n2); + pr.commit(n2); + pr.parse(ec); + if(n2 == 0) + { + pr.commit_eof(); + pr.parse(ec); + break; + } + } + return sink.get_body(); + } + + void + test_parser() + { + rts::context ctx; + std::vector encodings; + response_parser::config cfg; + + #ifdef BOOST_RTS_HAS_ZLIB + cfg.apply_deflate_decoder = true; + cfg.apply_gzip_decoder = true; + rts::zlib::install_deflate_service(ctx); + rts::zlib::install_inflate_service(ctx); + encodings.push_back("gzip"); + encodings.push_back("deflate"); + #endif + #ifdef BOOST_RTS_HAS_BROTLI + cfg.apply_brotli_decoder = true; + rts::brotli::install_encode_service(ctx); + rts::brotli::install_decode_service(ctx); + encodings.push_back("br"); + #endif + + cfg.body_limit = 1024 * 1024; + install_parser_service(ctx, cfg); + response_parser pr(ctx); + pr.reset(); + + auto append_chunked = []( + std::string& msg, + core::string_view body) + { + for(;;) + { + auto chunk = body.substr(0, + std::min(size_t{ 100 }, body.size())); + body.remove_prefix(chunk.size()); + msg.append(8, '0'); + auto it = msg.begin() + msg.size() - 8; + auto c = std::snprintf(&*it, 8, "%zx", chunk.size()); + msg.erase(it + c, msg.end()); + msg += "\r\n"; + msg += chunk; + msg += "\r\n"; + if(chunk.size() == 0) + break; + } + }; + + const auto rand_string = make_rand_string(1024 * 1024); + + for(core::string_view encoding : encodings) + for(core::string_view transfer : { "chunked", "sized", "to_eof" }) + for(auto body_size : { 0, 7, 64 * 1024, 1024 * 1024 }) + for(auto driver : { parser_pull_body, parser_sink_body, parser_elastic_body }) + { + std::string msg = "HTTP/1.1 200 OK\r\n"; + msg += "Content-Encoding: "; + msg += encoding; + msg += "\r\n"; + + auto body = core::string_view{ rand_string }.substr(0, body_size); + auto compressed_body = compress( + ctx, + encoding, + body); + + if(transfer == "chunked") + { + msg += "Transfer-Encoding: chunked\r\n"; + msg += "\r\n"; + append_chunked(msg, compressed_body); + } + else if(transfer == "sized") + { + msg += "Content-Length: " + + std::to_string(compressed_body.size()) + "\r\n"; + msg += "\r\n"; + msg += compressed_body; + } + else // to_eof + { + msg += "\r\n"; + msg += compressed_body; + } + + pr.start(); + pr.set_body_limit(body_size); + + auto rs = driver( + pr, + buffers::const_buffer(msg.data(), msg.size())); + + BOOST_TEST(rs == body); + + if(transfer == "to_eof") + pr.reset(); + } + } + + void run() + { + test_serializer(); + test_parser(); + } +}; + +TEST_SUITE( + zlib_test, + "boost.http_proto.compression"); + +} // namespace http_proto +} // namespace boost diff --git a/test/unit/fields.cpp b/test/unit/fields.cpp index b9333fe6..5be893b5 100644 --- a/test/unit/fields.cpp +++ b/test/unit/fields.cpp @@ -363,14 +363,6 @@ struct fields_test f.max_capacity_in_bytes(), 0); } - { - BOOST_TEST_THROWS( - fields(0, ~std::size_t{0}), std::length_error); - - BOOST_TEST_THROWS( - fields(1024, ~std::size_t{0}), std::length_error); - } - { std::size_t init = 4096; std::size_t cap = init; diff --git a/test/unit/natvis.cpp b/test/unit/natvis.cpp index e1f7a714..590321d7 100644 --- a/test/unit/natvis.cpp +++ b/test/unit/natvis.cpp @@ -16,6 +16,8 @@ #include "test_suite.hpp" +#include + namespace boost { namespace http_proto { @@ -31,6 +33,7 @@ struct natvis_test "Server: localhost\r\n" "\r\n"); request_view rv(req); + ignore_unused(rv); } { std::error_condition ec(std::errc::address_in_use); @@ -40,6 +43,7 @@ struct natvis_test "Server: localhost\r\n" "\r\n"); response_view rv(res); + ignore_unused(rv); } } }; diff --git a/test/unit/request.cpp b/test/unit/request.cpp index fbb72dad..7099756e 100644 --- a/test/unit/request.cpp +++ b/test/unit/request.cpp @@ -733,16 +733,6 @@ struct request_test f.max_capacity_in_bytes(), 0); } - { - BOOST_TEST_THROWS( - request(0, ~std::size_t{0}), - std::length_error); - - BOOST_TEST_THROWS( - request(1024, ~std::size_t{0}), - std::length_error); - } - { std::size_t init = 4096; std::size_t cap = init; diff --git a/test/unit/response.cpp b/test/unit/response.cpp index b759444b..13b6a585 100644 --- a/test/unit/response.cpp +++ b/test/unit/response.cpp @@ -380,16 +380,6 @@ class response_test f.max_capacity_in_bytes(), 0); } - { - BOOST_TEST_THROWS( - response(0, ~std::size_t{0}), - std::length_error); - - BOOST_TEST_THROWS( - response(1024, ~std::size_t{0}), - std::length_error); - } - { std::size_t init = 4096; std::size_t cap = init; diff --git a/test/unit/zlib.cpp b/test/unit/zlib.cpp deleted file mode 100644 index 0bbf2bb1..00000000 --- a/test/unit/zlib.cpp +++ /dev/null @@ -1,702 +0,0 @@ -// -// Copyright (c) 2024 Christian Mazakas -// Copyright (c) 2024 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 -// - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_helpers.hpp" - -#include -#include -#include - -// opt into random ascii generation as it's easier than -// maintaing a large static asset that has to be loaded -// at runtime -std::string -generate_book(std::size_t size) -{ - std::random_device rd; - std::mt19937 gen(rd()); - - std::uniform_int_distribution distrib(32, 127); - - std::string out(size, '\0'); - for(auto& o : out) - o = static_cast(distrib(gen)); - - return out; -}; - -namespace boost { -namespace http_proto { - -struct zlib_test -{ - std::string - deflate( - rts::zlib::deflate_service& d_svc, - int window_bits, - int mem_level, - core::string_view str) - { - namespace zlib = rts::zlib; - zlib::stream zs{}; - - auto ret = static_cast( - d_svc.init2( - zs, - zlib::default_compression, - zlib::deflated, - window_bits, - mem_level, - zlib::default_strategy)); - - if(!BOOST_TEST_EQ(ret, zlib::error::ok)) - { - d_svc.deflate_end(zs); - return {}; - } - - zs.next_in = reinterpret_cast(const_cast(str.data())); - zs.avail_in = static_cast(str.size()); - - std::string result; - for(;;) - { - constexpr auto chunk_size = 2048; - result.resize(result.size() + chunk_size); - auto it = result.begin() + result.size() - chunk_size; - zs.next_out = reinterpret_cast(&*it); - zs.avail_out = chunk_size; - ret = static_cast( - d_svc.deflate(zs, zlib::finish)); - result.erase( - it + chunk_size - zs.avail_out, result.end()); - if(ret != zlib::error::ok) - break; - } - d_svc.deflate_end(zs); - return result; - } - - void verify_compressed( - rts::zlib::inflate_service& i_svc, - span compressed, - core::string_view expected) - { - namespace zlib = rts::zlib; - - zlib::stream zs{}; - - int const window_bits = 15; // default - int const enable_zlib_and_gzip = 32; - auto ret = static_cast( - i_svc.init2( - zs, - window_bits + enable_zlib_and_gzip)); - if(! BOOST_TEST_EQ(ret, zlib::error::ok) ) - { - i_svc.inflate_end(zs); - return; - } - - std::vector decompressed_output( - 2 * expected.size() + 50, 0x00); - - zs.next_in = compressed.data(); - zs.avail_in = - static_cast(compressed.size()); - - zs.next_out = decompressed_output.data(); - zs.avail_out = - static_cast(decompressed_output.size()); - - ret = static_cast(i_svc.inflate(zs, zlib::finish)); - if(! BOOST_TEST_EQ(ret, zlib::error::stream_end) ) - { - i_svc.inflate_end(zs); - return; - } - - auto n = zs.next_out - decompressed_output.data(); - core::string_view sv2( - reinterpret_cast( - decompressed_output.data()), n); - BOOST_TEST(sv2 == expected); - - i_svc.inflate_end(zs); - return; - } - - static - buffers::mutable_buffer - zlib_serializer_source( - response_view res, - serializer& sr, - span body_view, - span output) - { - struct source_t : public source - { - span& body_view_; - source_t(span& bv) : body_view_(bv) {} - - results - on_read(buffers::mutable_buffer b) - { - results rs; - auto n = buffers::copy( - b, - buffers::const_buffer( - body_view_.data(), - body_view_.size())); - - body_view_ = body_view_.subspan(n); - rs.bytes = n; - rs.ec = {}; - rs.finished = body_view_.empty(); - return rs; - } - }; - - buffers::mutable_buffer output_buf( - output.data(), output.size()); - - auto& s = sr.start(res, body_view); - (void)s; - - while(! body_view.empty() || ! sr.is_done() ) - { - auto cbs = sr.prepare().value(); - BOOST_TEST_GT(buffers::size(cbs), 0); - - auto n2 = buffers::copy( - output_buf, cbs); - BOOST_TEST_EQ(n2, buffers::size(cbs)); - - sr.consume(n2); - output_buf = buffers::sans_prefix(output_buf, n2); - } - return output_buf; - } - - static - buffers::mutable_buffer - zlib_serializer_stream( - response_view res, - serializer& sr, - span body_view, - span output) - { - buffers::mutable_buffer output_buf( - output.data(), output.size()); - - auto stream = sr.start_stream(res); - - while(! body_view.empty() ) - { - auto mbs = stream.prepare(); - auto n = buffers::copy( - mbs, buffers::const_buffer( - body_view.data(), - std::min( - std::size_t{512}, - body_view.size()))); - body_view = body_view.subspan(n); - stream.commit(n); - - auto cbs = sr.prepare().value(); - BOOST_TEST_GT(buffers::size(cbs), 0); - - auto n2 = buffers::copy( - output_buf, cbs); - BOOST_TEST_EQ(n2, buffers::size(cbs)); - sr.consume(n2); - output_buf = buffers::sans_prefix(output_buf, n2); - } - - stream.close(); - - while(! sr.is_done() ) - { - auto cbs = sr.prepare().value(); - BOOST_TEST_GT(buffers::size(cbs), 0); - auto n = buffers::copy( - output_buf, cbs); - output_buf = buffers::sans_prefix(output_buf, n); - BOOST_TEST_EQ(n, buffers::size(cbs)); - sr.consume(n); - } - - return output_buf; - } - - static - buffers::mutable_buffer - zlib_serializer_buffers( - response_view res, - serializer& sr, - span body_view, - span output) - { - buffers::mutable_buffer output_buf( - output.data(), output.size()); - - std::vector buf_seq; - { - std::size_t num_bufs = 23; // random odd number - std::size_t buf_size = - body_view.size() / num_bufs; - std::size_t remaining = - body_view.size() % num_bufs; - - for( std::size_t i = 0; i < num_bufs; ++i ) - { - auto offset = i * buf_size; - buf_seq.push_back( - {body_view.data() + offset, buf_size}); - } - - if( remaining > 0 ) - { - auto offset = num_bufs * buf_size; - buf_seq.push_back( - {body_view.data() + offset, remaining}); - } - } - - buffers::const_buffer_span bufs( - buf_seq.data(), buf_seq.size()); - - sr.start(res, bufs); - - while(! sr.is_done() ) - { - auto cbs = sr.prepare().value(); - BOOST_TEST_GT(buffers::size(cbs), 0); - BOOST_TEST_LT( - buffers::size(cbs), - output_buf.size()); - - auto n = buffers::copy(output_buf, cbs); - BOOST_TEST_EQ(n, buffers::size(cbs)); - - output_buf = buffers::sans_prefix(output_buf, n); - sr.consume(n); - } - return output_buf; - } - - using fp_type = - buffers::mutable_buffer(*)( - response_view res, - serializer& sr, - span body_view, - span output); - - void zlib_serializer_impl( - fp_type fp, - core::string_view const c, - std::string const& body, - bool chunked_encoding) - { - namespace zlib = rts::zlib; - rts::context ctx; - zlib::install_deflate_service(ctx); - auto& i_svc = zlib::install_inflate_service(ctx); - serializer::config cfg; - cfg.apply_deflate_encoder = true; - cfg.apply_gzip_encoder = true; - install_serializer_service(ctx, cfg); - serializer sr(ctx); - - // prove we can reuse the serializer successfully - for( int i = 0; i < 2; ++i ) - { - sr.reset(); - - response res; - res.set("Content-Encoding", c); - res.set_chunked(chunked_encoding); - - std::string header; - { - header += "HTTP/1.1 200 OK\r\n"; - if( c == "deflate" ) - header += "Content-Encoding: deflate\r\n"; - if( c == "gzip" ) - header += "Content-Encoding: gzip\r\n"; - if( chunked_encoding ) - header += "Transfer-Encoding: chunked\r\n"; - - header += "\r\n"; - } - - core::string_view str = header; - std::vector output( - str.size() + 3 * body.size() + 50, 0x00); - - span body_view = body; - auto output_buf = - fp(res, sr, body_view, output); - - auto m = output.size() - output_buf.size(); - auto sv = - core::string_view( - reinterpret_cast( - output.data()), - m); - - BOOST_TEST(sv.starts_with(str)); - - sv = sv.substr(str.size()); - - std::vector compressed; - compressed.reserve(body_view.size()); - - while(! sv.empty() ) - { - if(! res.chunked() ) - { - compressed.insert( - compressed.end(), sv.begin(), sv.end()); - break; - } - - core::string_view& chunk = sv; - - auto pos = chunk.find_first_of("\r\n"); - BOOST_TEST_NE(pos, core::string_view::npos); - - std::string chunk_header = chunk.substr(0, pos); - chunk.remove_prefix(pos + 2); - - auto chunk_size = std::stoul( - chunk_header, nullptr, 16); - - if( chunk_size == 0 ) - { - BOOST_TEST(chunk == "\r\n"); - } - else - { - BOOST_TEST_LT( - chunk.begin() + chunk_size, - chunk.end()); - - compressed.insert( - compressed.end(), - chunk.data(), - chunk.data() + chunk_size); - - chunk.remove_prefix(chunk_size); - BOOST_TEST(chunk.starts_with("\r\n")); - } - chunk.remove_prefix(2); - } - - // BOOST_TEST_LT(compressed.size(), body.size()); - - verify_compressed(i_svc, compressed, body); - } - } - - void - test_serializer() - { - std::string empty_body = ""; - - std::string short_body = - "hello world, compression seems super duper cool! hmm, but what if I also add like a whole bunch of text to this thing????"; - - std::string long_body = - generate_book(350000); - - std::vector bodies = { - empty_body, - short_body, - long_body - }; - - std::vector coding_types = { - "deflate", - "gzip" - }; - - std::vector fps = { - zlib_serializer_stream, - zlib_serializer_source, - zlib_serializer_buffers - }; - - bool use_chunked_encoding[] = { - false, - true - }; - - for( auto fp : fps ) - for( auto body : bodies ) - for( auto c : coding_types ) - for( auto b : use_chunked_encoding ) - zlib_serializer_impl(fp, c, body, b); - } - - static - std::string - pull_body( - response_parser& pr, - buffers::const_buffer input) - { - std::string rs; - buffers::string_buffer buf(&rs); - for(;;) - { - auto n1 = buffers::copy( - pr.prepare(), input); - pr.commit(n1); - input = buffers::sans_prefix(input, n1); - - boost::system::error_code ec; - pr.parse(ec); - if(!ec) - pr.parse(ec); - if(ec) - BOOST_TEST(ec == error::in_place_overflow - || ec == error::need_data); - - // consume in_place body - auto n2 = buffers::copy( - buf.prepare(buffers::size(pr.pull_body())), - pr.pull_body()); - buf.commit(n2); - pr.consume_body(n2); - - if( input.size() == 0 && ec == error::need_data ) - { - pr.commit_eof(); - pr.parse(ec); - BOOST_TEST(!ec || ec == error::in_place_overflow); - } - if( pr.is_complete() ) - break; - } - return rs; - } - - static - std::string - elastic_body( - response_parser& pr, - buffers::const_buffer input) - { - std::string rs; - std::size_t n1 = buffers::copy( - pr.prepare(), input); - input = buffers::sans_prefix(input, n1); - pr.commit(n1); - system::error_code ec; - pr.parse(ec); - BOOST_TEST(pr.got_header()); - - buffers::string_buffer buf(&rs); - pr.set_body(std::ref(buf)); - pr.parse(ec); - - while(ec == error::need_data) - { - std::size_t n2 = buffers::copy( - pr.prepare(), input); - input = buffers::sans_prefix(input, n2); - pr.commit(n2); - pr.parse(ec); - if(n2 == 0) - { - pr.commit_eof(); - pr.parse(ec); - break; - } - } - return rs; - } - - static - std::string - sink_body( - response_parser& pr, - buffers::const_buffer input) - { - std::size_t n1 = buffers::copy( - pr.prepare(), input); - input = buffers::sans_prefix(input, n1); - pr.commit(n1); - system::error_code ec; - pr.parse(ec); - BOOST_TEST(pr.got_header()); - - class sink_t : public sink - { - std::string body_; - - public: - std::string - get_body() - { - return body_; - } - - results - on_write( - buffers::const_buffer b, - bool) override - { - body_.append( - static_cast(b.data()), - b.size()); - results rv; - rv.bytes = b.size(); - return rv; - } - }; - - auto& sink = pr.set_body(); - pr.parse(ec); - - while(ec == error::need_data) - { - std::size_t n2 = buffers::copy( - pr.prepare(), input); - input = buffers::sans_prefix(input, n2); - pr.commit(n2); - pr.parse(ec); - if(n2 == 0) - { - pr.commit_eof(); - pr.parse(ec); - break; - } - } - return sink.get_body(); - } - - void - test_parser() - { - rts::context ctx; - rts::zlib::install_inflate_service(ctx); - auto& d_svc = rts::zlib::install_deflate_service(ctx); - - auto append_chunked = []( - std::string& msg, - core::string_view body) - { - for(;;) - { - auto chunk = body.substr(0, - std::min(size_t{ 100 }, body.size())); - body.remove_prefix(chunk.size()); - msg.append(8, '0'); - auto it = msg.begin() + msg.size() - 8; - auto c = std::snprintf(&*it, 8, "%zx", chunk.size()); - msg.erase(it + c, msg.end()); - msg += "\r\n"; - msg += chunk; - msg += "\r\n"; - if(chunk.size() == 0) - break; - } - }; - - response_parser::config cfg; - cfg.apply_deflate_decoder = true; - cfg.apply_gzip_decoder = true; - cfg.body_limit = 1024 * 1024; - install_parser_service(ctx, cfg); - response_parser pr(ctx); - pr.reset(); - - for(std::string encoding : { "gzip", "deflate" }) - for(std::string transfer : { "chunked", "sized", "to_eof" }) - for(auto body_size : { 0, 7, 64 * 1024, 1024 * 1024 }) - for(auto receiver : { pull_body, sink_body, elastic_body }) - { - std::string msg = - "HTTP/1.1 200 OK\r\n" - "Content-Encoding: " + encoding + "\r\n"; - - auto body = generate_book(body_size); - auto deflated_body = deflate( - d_svc, - 15 + (encoding == "gzip" ? 16 : 0), - 8, - body); - - if(transfer == "chunked") - { - msg += "Transfer-Encoding: chunked\r\n"; - msg += "\r\n"; - append_chunked(msg, deflated_body); - } - else if(transfer == "sized") - { - msg += "Content-Length: " + - std::to_string(deflated_body.size()) + "\r\n"; - msg += "\r\n"; - msg += deflated_body; - } - else // to_eof - { - msg += "\r\n"; - msg += deflated_body; - } - - pr.start(); - pr.set_body_limit(body_size); - - auto rs = receiver( - pr, - buffers::const_buffer( - msg.data(), msg.size())); - - BOOST_TEST(rs == body); - - if(transfer == "to_eof") - pr.reset(); - } - } - - void run() - { - #ifdef BOOST_RTS_HAS_ZLIB - test_serializer(); - test_parser(); - #endif - } -}; - -TEST_SUITE( - zlib_test, - "boost.http_proto.zlib"); - -} // namespace http_proto -} // namespace boost