diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f818483 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/test/www*/dir/*.html text eol=lf +/test/www*/dir/*.txt text eol=lf \ No newline at end of file diff --git a/.github/workflows/abidiff.yaml b/.github/workflows/abidiff.yaml new file mode 100644 index 0000000..186e4fc --- /dev/null +++ b/.github/workflows/abidiff.yaml @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 Andrea Pappacoda +# SPDX-License-Identifier: MIT + +name: abidiff + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +defaults: + run: + shell: sh + +jobs: + abi: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + container: + image: debian:testing + + steps: + - name: Install dependencies + run: apt -y --update install --no-install-recommends + abigail-tools + ca-certificates + g++ + git + libbrotli-dev + libssl-dev + meson + pkg-config + python3 + zlib1g-dev + + - uses: actions/checkout@v4 + with: + path: current + + - uses: actions/checkout@v4 + with: + path: previous + fetch-depth: 0 + + - name: Checkout previous + working-directory: previous + run: | + git switch master + git describe --tags --abbrev=0 master | xargs git checkout + + - name: Build current + working-directory: current + run: | + meson setup --buildtype=debug -Dcpp-httplib_compile=true build + ninja -C build + + - name: Build previous + working-directory: previous + run: | + meson setup --buildtype=debug -Dcpp-httplib_compile=true build + ninja -C build + + - name: Run abidiff + run: abidiff + --headers-dir1 previous/build + --headers-dir2 current/build + previous/build/libcpp-httplib.so + current/build/libcpp-httplib.so diff --git a/.github/workflows/cifuzz.yaml b/.github/workflows/cifuzz.yaml new file mode 100644 index 0000000..422b58d --- /dev/null +++ b/.github/workflows/cifuzz.yaml @@ -0,0 +1,32 @@ +name: CIFuzz + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'cpp-httplib' + dry-run: false + language: c++ + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'cpp-httplib' + fuzz-seconds: 600 + dry-run: false + language: c++ + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3340f17..296ad29 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,17 +1,153 @@ name: test -on: [push, pull_request] +on: + push: + pull_request: + workflow_dispatch: + inputs: + gtest_filter: + description: 'Google Test filter' + test_linux: + description: 'Test on Linux' + type: boolean + default: true + test_macos: + description: 'Test on MacOS' + type: boolean + default: true + test_windows: + description: 'Test on Windows' + type: boolean + default: true + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +env: + GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }} jobs: - build: - runs-on: ${{ matrix.os }} + style-check: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + continue-on-error: true + steps: + - name: checkout + uses: actions/checkout@v4 + - name: run style check + run: | + clang-format --version + cd test && make style_check + ubuntu: + runs-on: ubuntu-latest + if: > + (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true') strategy: matrix: - os: [macOS-latest, ubuntu-latest] - + config: + - arch_flags: -m32 + arch_suffix: :i386 + name: (32-bit) + - arch_flags: + arch_suffix: + name: (64-bit) + name: ubuntu ${{ matrix.config.name }} steps: - - name: checkout - uses: actions/checkout@v1 - - name: make - run: cd test && make + - name: checkout + uses: actions/checkout@v4 + - name: install libraries + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \ + libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \ + zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \ + libzstd-dev${{ matrix.config.arch_suffix }} + - name: build and run tests + run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" + - name: run fuzz test target + run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" fuzz_test + + macos: + runs-on: macos-latest + if: > + (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true') + steps: + - name: checkout + uses: actions/checkout@v4 + - name: build and run tests + run: cd test && make + - name: run fuzz test target + run: cd test && make fuzz_test + + windows: + runs-on: windows-latest + if: > + (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true') + strategy: + matrix: + config: + - with_ssl: false + compiled: false + run_tests: true + name: without SSL + - with_ssl: true + compiled: false + run_tests: true + name: with SSL + - with_ssl: false + compiled: true + run_tests: false + name: compiled + name: windows ${{ matrix.config.name }} + steps: + - name: Prepare Git for Checkout on Windows + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout + uses: actions/checkout@v4 + - name: Export GitHub Actions cache environment variables + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Setup msbuild on windows + uses: microsoft/setup-msbuild@v2 + - name: Install vcpkg dependencies + run: vcpkg install gtest curl zlib brotli zstd + - name: Install OpenSSL + if: ${{ matrix.config.with_ssl }} + run: choco install openssl + - name: Configure CMake ${{ matrix.config.name }} + run: > + cmake -B build -S . + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake + -DHTTPLIB_TEST=ON + -DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }} + -DHTTPLIB_REQUIRE_ZLIB=ON + -DHTTPLIB_REQUIRE_BROTLI=ON + -DHTTPLIB_REQUIRE_ZSTD=ON + -DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }} + - name: Build ${{ matrix.config.name }} + run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine + - name: Run tests ${{ matrix.config.name }} + if: ${{ matrix.config.run_tests }} + run: ctest --output-on-failure --test-dir build -C Release + + env: + VCPKG_ROOT: "C:/vcpkg" + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" diff --git a/.gitignore b/.gitignore index f104251..1ae8acf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,19 +3,33 @@ tags example/server example/client example/hello +example/simplecli example/simplesvr example/benchmark example/redirect +example/sse* example/upload +example/one_time_request +example/server_and_client example/*.pem +test/httplib.cc +test/httplib.h test/test +test/server_fuzzer +test/test_proxy +test/test_split test/test.xcodeproj/xcuser* test/test.xcodeproj/*/xcuser* +test/*.o test/*.pem test/*.srl +test/_build_* +work/ +benchmark/server* *.swp +build/ Debug Release *.vcxproj.user @@ -25,5 +39,7 @@ Release *.db ipch *.dSYM +*.pyc .* +!/.gitattributes !/.travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3727d8a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Environment -language: cpp -os: - - osx - -# Compiler selection -compiler: - - clang - -# Build/test steps -script: - - cd ${TRAVIS_BUILD_DIR}/test - - make all diff --git a/CMakeLists.txt b/CMakeLists.txt index a06cf35..b0e0fda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,340 @@ -cmake_minimum_required(VERSION 3.7.0) -project(httplib) +#[[ + Build options: + * BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON) + * HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on) + * HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on) + * HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on) + * HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on) + * HTTPLIB_REQUIRE_OPENSSL (default off) + * HTTPLIB_REQUIRE_ZLIB (default off) + * HTTPLIB_REQUIRE_BROTLI (default off) + * HTTPLIB_REQUIRE_ZSTD (default off) + * HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on) + * HTTPLIB_COMPILE (default off) + * HTTPLIB_INSTALL (default on) + * HTTPLIB_TEST (default off) + * BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed). + * OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed). -set(CMAKE_CXX_STANDARD 11) + ------------------------------------------------------------------------------- -# Include + After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli zstd) is available. + This creates a httplib::httplib target (if found and if listed components are supported). + It can be linked like so: + + target_link_libraries(your_exe httplib::httplib) + + The following will build & install for later use. + + Linux/macOS: + + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + sudo cmake --build . --target install + + Windows: + + mkdir build + cd build + cmake .. + runas /user:Administrator "cmake --build . --config Release --target install" + + ------------------------------------------------------------------------------- + + These variables are available after you run find_package(httplib) + * HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h). + * HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled. + * HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled. + * HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled. + * HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled. + * HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled. + * HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only. + * HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include). + * HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so). + * httplib_VERSION or HTTPLIB_VERSION - the project's version string. + * HTTPLIB_FOUND - a bool for if the target was found. + + Want to use precompiled headers (Cmake feature since v3.16)? + It's as simple as doing the following (before linking): + + target_precompile_headers(httplib::httplib INTERFACE "${HTTPLIB_HEADER_PATH}") + + ------------------------------------------------------------------------------- + + ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14 +]] +cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR) + +# Get the CPPHTTPLIB_VERSION value and use it as a version +# This gets the string with the CPPHTTPLIB_VERSION value from the header. +# This is so the maintainer doesn't actually need to update this manually. +file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"") + +# Extracts just the version string itself from the whole string contained in _raw_version_string +# since _raw_version_string would contain the entire line of code where it found the version string +string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}") + +project(httplib + VERSION ${_httplib_version} + LANGUAGES CXX + DESCRIPTION "A C++ header-only HTTP/HTTPS server and client library." + HOMEPAGE_URL "https://github.com/yhirose/cpp-httplib" +) + +# Change as needed to set an OpenSSL minimum version. +# This is used in the installed Cmake config file. +set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0") + +# Lets you disable C++ exception during CMake configure time. +# The value is used in the install CMake config file. +option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF) +# Allow for a build to require OpenSSL to pass, instead of just being optional +option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF) +option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF) +# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found. +# Make these options so their automatic use can be specifically disabled (as needed) +option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON) +option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON) +# Lets you compile the program as a regular library instead of header-only +option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF) +# Lets you disable the installation (useful when fetched from another CMake project) +option(HTTPLIB_INSTALL "Enables the installation target" ON) +option(HTTPLIB_TEST "Enables testing and builds tests" OFF) +option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF) +option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON) +option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON) +option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF) +option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON) +# Defaults to static library +option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF) +if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE) + # Necessary for Windows if building shared libs + # See https://stackoverflow.com/a/40743080 + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +# Set some variables that are used in-tree and while building based on our options +set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE}) +set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}) + +# Threads needed for on some systems, and for on Linux +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) +# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS. +if(HTTPLIB_REQUIRE_OPENSSL) + find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED) + set(HTTPLIB_IS_USING_OPENSSL TRUE) +elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE) + find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET) + # Avoid a rare circumstance of not finding all components but the end-user did their + # own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted + if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto) + set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND}) + else() + set(HTTPLIB_IS_USING_OPENSSL FALSE) + endif() +endif() + +if(HTTPLIB_REQUIRE_ZLIB) + find_package(ZLIB REQUIRED) + set(HTTPLIB_IS_USING_ZLIB TRUE) +elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE) + find_package(ZLIB QUIET) + # FindZLIB doesn't have a ZLIB_FOUND variable, so check the target. + if(TARGET ZLIB::ZLIB) + set(HTTPLIB_IS_USING_ZLIB TRUE) + endif() +endif() + +# Adds our cmake folder to the search path for find_package +# This is so we can use our custom FindBrotli.cmake +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +if(HTTPLIB_REQUIRE_BROTLI) + find_package(Brotli COMPONENTS encoder decoder common REQUIRED) + set(HTTPLIB_IS_USING_BROTLI TRUE) +elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE) + find_package(Brotli COMPONENTS encoder decoder common QUIET) + set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND}) +endif() + +if(HTTPLIB_REQUIRE_ZSTD) + find_package(zstd) + if(NOT zstd_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() + set(HTTPLIB_IS_USING_ZSTD TRUE) +elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE) + find_package(zstd QUIET) + if(NOT zstd_FOUND) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd) + + if(TARGET PkgConfig::zstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() + endif() + endif() + # Both find_package and PkgConf set a XXX_FOUND var + set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND}) +endif() + +# Used for default, common dirs that the end-user can change (if needed) +# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR include(GNUInstallDirs) -include(ExternalProject) -add_library(${PROJECT_NAME} INTERFACE) -target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11) +if(HTTPLIB_COMPILE) + # Put the split script into the build dir + configure_file(split.py "${CMAKE_CURRENT_BINARY_DIR}/split.py" + COPYONLY + ) + # Needs to be in the same dir as the python script + configure_file(httplib.h "${CMAKE_CURRENT_BINARY_DIR}/httplib.h" + COPYONLY + ) -target_include_directories(${PROJECT_NAME} INTERFACE - $ - $) + # Used outside of this if-else + set(_INTERFACE_OR_PUBLIC PUBLIC) + # Brings in the Python3_EXECUTABLE path we can use. + find_package(Python3 REQUIRED) + # Actually split the file + # Keeps the output in the build dir to not pollute the main dir + execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/split.py" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ERROR_VARIABLE _httplib_split_error + ) + if(_httplib_split_error) + message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}") + endif() -install(TARGETS ${PROJECT_NAME} EXPORT httplibConfig - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + # split.py puts output in "out" + set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out") + # This will automatically be either static or shared based on the value of BUILD_SHARED_LIBS + add_library(${PROJECT_NAME} "${_httplib_build_includedir}/httplib.cc") + target_sources(${PROJECT_NAME} + PUBLIC + $ + $ + ) + set_target_properties(${PROJECT_NAME} + PROPERTIES + VERSION ${${PROJECT_NAME}_VERSION} + SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}" + OUTPUT_NAME cpp-httplib + ) +else() + # This is for header-only. + set(_INTERFACE_OR_PUBLIC INTERFACE) + add_library(${PROJECT_NAME} INTERFACE) + set(_httplib_build_includedir "${CMAKE_CURRENT_SOURCE_DIR}") +endif() +# Lets you address the target with httplib::httplib +# Only useful if building in-tree, versus using it from an installation. +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) +# Require C++11 +target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11) -install(EXPORT httplibConfig DESTINATION share/httplib/cmake) +target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC} + $ + $ +) -export(TARGETS ${PROJECT_NAME} FILE httplibConfig.cmake) +# Always require threads +target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} + Threads::Threads + # Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered. + $<$:ws2_32> + $<$:crypt32> + # Needed for API from MacOS Security framework + "$<$,$,$>:-framework CoreFoundation -framework Security>" + # Can't put multiple targets in a single generator expression or it bugs out. + $<$:Brotli::common> + $<$:Brotli::encoder> + $<$:Brotli::decoder> + $<$:ZLIB::ZLIB> + $<$:zstd::libzstd> + $<$:OpenSSL::SSL> + $<$:OpenSSL::Crypto> +) -#add_subdirectory(example) -#add_subdirectory(test) \ No newline at end of file +# Set the definitions to enable optional features +target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} + $<$:CPPHTTPLIB_NO_EXCEPTIONS> + $<$:CPPHTTPLIB_BROTLI_SUPPORT> + $<$:CPPHTTPLIB_ZLIB_SUPPORT> + $<$:CPPHTTPLIB_ZSTD_SUPPORT> + $<$:CPPHTTPLIB_OPENSSL_SUPPORT> + $<$,$,$>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN> +) + +# CMake configuration files installation directory +set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + +include(CMakePackageConfigHelpers) + +# Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc. +configure_package_config_file("cmake/${PROJECT_NAME}Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}" + # Passes the includedir install path + PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR +) + +if(HTTPLIB_COMPILE) + write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" + # Example: if you find_package(httplib 0.5.4) + # then anything >= 0.5.4 and < 0.6 is accepted + COMPATIBILITY SameMinorVersion + ) +else() + write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" + # Example: if you find_package(httplib 0.5.4) + # then anything >= 0.5.4 and < 0.6 is accepted + COMPATIBILITY SameMinorVersion + # Tells Cmake that it's a header-only lib + # Mildly useful for end-users :) + ARCH_INDEPENDENT + ) +endif() + +if(HTTPLIB_INSTALL) + # Creates the export httplibTargets.cmake + # This is strictly what holds compilation requirements + # and linkage information (doesn't find deps though). + install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets) + + install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE) + + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + # Install it so it can be used later by the httplibConfig.cmake file. + # Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages. + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake" + DESTINATION ${_TARGET_INSTALL_CMAKEDIR} + ) + + # NOTE: This path changes depending on if it's on Windows or Linux + install(EXPORT httplibTargets + # Puts the targets into the httplib namespace + # So this makes httplib::httplib linkable after doing find_package(httplib) + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${_TARGET_INSTALL_CMAKEDIR} + ) + + # Install documentation & license + # ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE + install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}") + install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}") + + include(CPack) +endif() + +if(HTTPLIB_TEST) + include(CTest) + add_subdirectory(test) +endif() diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4abae17 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM yhirose4dockerhub/ubuntu-builder AS builder +WORKDIR /build +COPY httplib.h . +COPY docker/main.cc . +RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server + +FROM scratch +COPY --from=builder /build/server /server +COPY docker/html/index.html /html/index.html +EXPOSE 80 +CMD ["/server"] diff --git a/README.md b/README.md index d05c31c..e85f9bc 100644 --- a/README.md +++ b/README.md @@ -2,39 +2,149 @@ cpp-httplib =========== [![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) -[![Build Status](https://travis-ci.org/yhirose/cpp-httplib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-httplib) -[![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-httplib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-httplib) -A C++ single-file header-only cross platform HTTP/HTTPS library. +A C++11 single-file header-only cross platform HTTP/HTTPS library. -It's extremely easy to setup. Just include **httplib.h** file in your code! +It's extremely easy to setup. Just include the **httplib.h** file in your code! -Server Example --------------- +> [!IMPORTANT] +> This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://yhirose.github.io"); + +// HTTPS +httplib::Client cli("https://yhirose.github.io"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +> [!NOTE] +> cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information. + +> [!TIP] +> For macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); + +// Disable host verification +cli.enable_server_hostname_verification(false); +``` + +> [!NOTE] +> When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ ```c++ #include int main(void) { - using namespace httplib; + using namespace httplib; - Server svr; + Server svr; - svr.Get("/hi", [](const Request& req, Response& res) { - res.set_content("Hello World!", "text/plain"); - }); + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); - svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { - auto numbers = req.matches[1]; - res.set_content(numbers, "text/plain"); - }); + // Match the request path against a regular expression + // and extract its captures + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); - svr.Get("/stop", [&](const Request& req, Response& res) { - svr.stop(); - }); + // Capture the second segment of the request path as "id" path param + svr.Get("/users/:id", [&](const Request& req, Response& res) { + auto user_id = req.path_params.at("id"); + res.set_content(user_id, "text/plain"); + }); - svr.listen("localhost", 1234); + // Extract values from HTTP headers and URL query params + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + // If the handler takes time to finish, you can also poll the connection state + svr.Get("/task", [&](const Request& req, Response& res) { + const char * result = nullptr; + process.run(); // for example, starting an external process + while (result == nullptr) { + sleep(1); + if (req.is_connection_closed()) { + process.kill(); // kill the process + return; + } + result = process.stdout(); // != nullptr if the process finishes + } + res.set_content(result, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); } ``` @@ -50,34 +160,144 @@ svr.listen_after_bind(); ### Static File Server ```cpp -svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`; +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); ``` ```cpp -svr.set_base_dir("./www", "/public"); +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); ``` +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +> [!WARNING] +> These static file server methods are not thread-safe. + +### File request handler + ```cpp -svr.set_base_dir("./www1", "/public"); // 1st order -svr.set_base_dir("./www2", "/public"); // 2nd order +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); ``` ### Logging ```cpp svr.set_logger([](const auto& req, const auto& res) { - your_logger(req, res); + your_logger(req, res); }); ``` -### Error Handler +### Error handler ```cpp svr.set_error_handler([](const auto& req, auto& res) { - auto fmt = "

Error Status: %d

"; - char buf[BUFSIZ]; - snprintf(buf, sizeof(buf), fmt, res.status); - res.set_content(buf, "text/html"); + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = StatusCode::InternalServerError_500; +}); +``` + +> [!CAUTION] +> if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### Pre request handler + +```cpp +svr.set_pre_request_handler([](const auto& req, auto& res) { + if (req.matched_route == "/user/:user") { + auto user = req.path_params.at("user"); + if (user != "john") { + res.status = StatusCode::Forbidden_403; + res.set_content("error", "text/html"); + return Server::HandlerResponse::Handled; + } + } + return Server::HandlerResponse::Unhandled; }); ``` @@ -85,40 +305,22 @@ svr.set_error_handler([](const auto& req, auto& res) { ```cpp svr.Post("/multipart", [&](const auto& req, auto& res) { - auto size = req.files.size(); - auto ret = req.has_file("name1"); - const auto& file = req.get_file_value("name1"); - // file.filename; - // file.content_type; - // file.content; -}); - -``` - -### Send content with Content provider - -```cpp -const uint64_t DATA_CHUNK_SIZE = 4; - -svr.Get("/stream", [&](const Request &req, Response &res) { - auto data = new std::string("abcdefg"); - - res.set_content_provider( - data->size(), // Content length - [data](uint64_t offset, uint64_t length, DataSink sink) { - const auto &d = *data; - sink(&d[offset], std::min(length, DATA_CHUNK_SIZE)); - }, - [data] { delete data; }); + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; }); ``` -### Receive content with Content receiver +### Receive content with a content receiver ```cpp svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read MultipartFormDataItems files; content_reader( [&](const MultipartFormData &file) { @@ -135,42 +337,170 @@ svr.Post("/content_receiver", body.append(data, data_length); return true; }); - res.set_content(body, "text/plain"); } }); ``` +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [&, data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + ### Chunked transfer encoding ```cpp svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_chunked_content_provider( - [](uint64_t offset, DataSink sink, Done done) { - sink("123", 3); - sink("345", 3); - sink("789", 3); - done(); + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. } ); }); ``` +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### Send file content + +```cpp +svr.Get("/content", [&](const Request &req, Response &res) { + res.set_file_content("./path/to/content.html"); +}); + +svr.Get("/content", [&](const Request &req, Response &res) { + res.set_file_content("./path/to/content", "text/html"); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return StatusCode::ExpectationFailed_417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = StatusCode::Unauthorized_401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +> [!NOTE] +> When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH`. + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + ### Default thread pool support -Set thread count to 8: +`ThreadPool` is used as the **default** task queue, with a default thread count of 8 or `std::thread::hardware_concurrency() - 1`, whichever is greater. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. ```cpp -#define CPPHTTPLIB_THREAD_POOL_COUNT 8 +svr.new_task_queue = [] { return new ThreadPool(12); }; ``` -Disable the default thread pool: +You can also provide an optional parameter to limit the maximum number +of pending requests, i.e. requests `accept()`ed by the listener but +still waiting to be serviced by worker threads. ```cpp -#define CPPHTTPLIB_THREAD_POOL_COUNT 0 +svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); }; ``` +Default limit is 0 (unlimited). Once the limit is reached, the listener +will shutdown the client connection. + ### Override the default thread pool with yours +You can supply your own thread pool implementation according to your need. + ```cpp class YourThreadPoolTaskQueue : public TaskQueue { public: @@ -178,8 +508,10 @@ public: pool_.start_with_thread_count(n); } - virtual void enqueue(std::function fn) override { - pool_.enqueue(fn); + virtual bool enqueue(std::function fn) override { + /* Return true if the task was actually enqueued, or false + * if the caller must drop the corresponding connection. */ + return pool_.enqueue(fn); } virtual void shutdown() override { @@ -195,10 +527,8 @@ svr.new_task_queue = [] { }; ``` -Client Example --------------- - -### GET +Client +------ ```c++ #include @@ -206,36 +536,72 @@ Client Example int main(void) { - httplib::Client cli("localhost", 1234); + httplib::Client cli("localhost", 1234); - auto res = cli.Get("/hi"); - if (res && res->status == 200) { - std::cout << res->body << std::endl; + if (auto res = cli.Get("/hi")) { + if (res->status == StatusCode::OK_200) { + std::cout << res->body << std::endl; } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } } ``` +> [!TIP] +> Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + ### GET with HTTP headers ```c++ - httplib::Headers headers = { - { "Accept-Encoding", "gzip, deflate" } - }; - auto res = cli.Get("/hi", headers); +httplib::Headers headers = { + { "Hello", "World!" } +}; +auto res = cli.Get("/hi", headers); ``` - -### GET with Content Receiver - +or ```c++ - std::string body; - - auto res = cli.Get("/large-data", - [&](const char *data, uint64_t data_length) { - body.append(data, data_length); - return true; - }); - - assert(res->body.empty()); +auto res = cli.Get("/hi", {{"Hello", "World!"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Hello", "World!" } +}); +auto res = cli.Get("/hi"); ``` ### POST @@ -268,15 +634,15 @@ auto res = cli.Post("/post", params); ### POST with Multipart Form Data ```c++ - httplib::MultipartFormDataItems items = { - { "text1", "text default", "", "" }, - { "text2", "aωb", "", "" }, - { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, - { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, - { "file3", "", "", "application/octet-stream" }, - }; +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; - auto res = cli.Post("/multipart", items); +auto res = cli.Post("/multipart", items); ``` ### PUT @@ -298,31 +664,90 @@ res = cli.Options("*"); res = cli.Options("/resource/foo"); ``` -### Connection Timeout +### Timeout ```c++ -cli.set_timeout_sec(5); // timeouts in 5 seconds +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds + +// This method works the same as curl's `--max-timeout` option +svr.set_max_timeout(5000); // 5 seconds ``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(StatusCode::OK_200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + ### With Progress Callback ```cpp -httplib::Client client(url, port); +httplib::Client cli(url, port); // prints: 0 / 000 bytes => 50% complete -std::shared_ptr res = - cli.Get("/", [](uint64_t len, uint64_t total) { - printf("%lld / %lld bytes => %d%% complete\n", - len, total, - (int)((len/total)*100)); - return true; // return 'false' if you want to cancel the request. - } +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} ); ``` ![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) -This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23). - ### Authentication ```cpp @@ -331,9 +756,13 @@ cli.set_basic_auth("user", "pass"); // Digest Authentication cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); ``` -NOTE: OpenSSL is required for Digest Authentication. +> [!NOTE] +> OpenSSL is required for Digest Authentication. ### Proxy server support @@ -345,9 +774,13 @@ cli.set_proxy_basic_auth("user", "pass"); // Digest Authentication cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); ``` -NOTE: OpenSSL is required for Digest Authentication. +> [!NOTE] +> OpenSSL is required for Digest Authentication. ### Range @@ -370,20 +803,15 @@ httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' ### Keep-Alive connection ```cpp -cli.set_keep_alive_max_count(2); // Default is 5 +httplib::Client cli("localhost", 1234); -std::vector requests; -Get(requests, "/get-request1"); -Get(requests, "/get-request2"); -Post(requests, "/post-request1", "text", "text/plain"); -Post(requests, "/post-request2", "text", "text/plain"); +cli.Get("/hello"); // with "Connection: close" -std::vector responses; -if (cli.send(requests, responses)) { - for (const auto& res: responses) { - ... - } -} +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" ``` ### Redirect @@ -399,67 +827,164 @@ res = cli.Get("/"); res->status; // 200 ``` -### Use a specitic network interface +### Use a specific network interface -NOTE: This feature is not available on Windows, yet. +> [!NOTE] +> This feature is not available on Windows, yet. ```cpp cli.set_interface("eth0"); // Interface name, IP address or host name ``` -OpenSSL Support ---------------- +Compression +----------- -SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. +The server can apply compression to the following MIME type contents: -NOTE: cpp-httplib supports 1.1.1 (until 2023-09-11) and 1.0.2 (2019-12-31). - -```c++ -#define CPPHTTPLIB_OPENSSL_SUPPORT - -SSLServer svr("./cert.pem", "./key.pem"); - -SSLClient cli("localhost", 8080); -cli.set_ca_cert_path("./ca-bundle.crt"); -cli.enable_server_certificate_verification(true); -``` - -Zlib Support ------------- - -'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. - -The server applies gzip compression to the following MIME type contents: - - * all text types + * all text types except text/event-stream * image/svg+xml * application/javascript * application/json * application/xml * application/xhtml+xml -### Compress content on client +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Default `Accept-Encoding` value + +The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. + +```c++ +res = cli.Get("/resource/foo"); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +``` + +If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. + +```c++ +res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}}); +``` + +### Compress request body on client ```c++ cli.set_compress(true); res = cli.Post("/resource/foo", "...", "text/plain"); ``` +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo"); +res->body; // Compressed data + +``` + +Unix Domain Socket Support +-------------------------- + +Unix Domain Socket support is available on Linux and macOS. + +```c++ +// Server +httplib::Server svr; +svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); + +// Client +httplib::Client cli("./my-socket.sock"); +cli.set_address_family(AF_UNIX); +``` + +"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. + + Split httplib.h into .h and .cc ------------------------------- +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +Dockerfile for Static HTTP Server +--------------------------------- + +Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container. + ```bash -> python3 split.py -> ls out -httplib.h httplib.cc +> docker build -t cpp-httplib-server . +... + +> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server +Serving HTTP on 0.0.0.0 port 80 ... +192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." +``` + +From Docker Hub + +```bash +> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server +Serving HTTP on 0.0.0.0 port 80 ... +192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." ``` NOTE ---- +### g++ + g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +> [!NOTE] +> cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +> [!NOTE] +> Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. + License ------- -MIT license (© 2019 Yuji Hirose) +MIT license (© 2025 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 0b3bc9e..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 1.0.{build} -image: Visual Studio 2017 -build_script: -- cmd: >- - cd test - - msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Debug;Platform=Win32 -test_script: -- cmd: Debug\test.exe \ No newline at end of file diff --git a/benchmark/Makefile b/benchmark/Makefile new file mode 100644 index 0000000..fa4f76c --- /dev/null +++ b/benchmark/Makefile @@ -0,0 +1,77 @@ +CXXFLAGS = -std=c++11 -O2 -I.. + +CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16 + +BENCH = bombardier -c 10 -d 5s localhost:8080 +MONITOR = ali http://localhost:8080 + +# cpp-httplib +bench: server + @echo "--------------------\n cpp-httplib latest\n--------------------\n" + @./server & export PID=$$!; $(BENCH); kill $${PID} + @echo "" + +monitor: server + @./server & export PID=$$!; $(MONITOR); kill $${PID} + +run : server + @./server + +server : cpp-httplib/main.cpp ../httplib.h + @g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp + +# cpp-httplib v0.19.0 +bench-v19: server-v19 + @echo "---------------------\n cpp-httplib v0.19.0\n---------------------\n" + @./server-v19 & export PID=$$!; $(BENCH); kill $${PID} + @echo "" + +monitor-v19: server-v19 + @./server-v19 & export PID=$$!; $(MONITOR); kill $${PID} + +run-v19 : server-v19 + @./server-v19 + +server-v19 : cpp-httplib-v19/main.cpp cpp-httplib-v19/httplib.h + @g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v19/main.cpp + +# cpp-httplib v0.18.0 +bench-v18: server-v18 + @echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n" + @./server-v18 & export PID=$$!; $(BENCH); kill $${PID} + @echo "" + +monitor-v18: server-v18 + @./server-v18 & export PID=$$!; $(MONITOR); kill $${PID} + +run-v18 : server-v18 + @./server-v18 + +server-v18 : cpp-httplib-v18/main.cpp cpp-httplib-v18/httplib.h + @g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v18/main.cpp + +# crow +bench-crow: server-crow + @echo "-------------\n Crow v1.2.0\n-------------\n" + @./server-crow & export PID=$$!; $(BENCH); kill $${PID} + @echo "" + +monitor-crow: server-crow + @./server-crow & export PID=$$!; $(MONITOR); kill $${PID} + +run-crow : server-crow + @./server-crow + +server-crow : crow/main.cpp + @g++ -o $@ $(CXXFLAGS) crow/main.cpp + +# misc +build: server server-v18 server-v19 server-crow + +bench-all: bench-crow bench bench-v19 bench-v18 + +issue: + bombardier -c 10 -d 30s localhost:8080 + +clean: + rm -rf server* diff --git a/benchmark/cpp-httplib-v18/httplib.h b/benchmark/cpp-httplib-v18/httplib.h new file mode 100644 index 0000000..3e28fcf --- /dev/null +++ b/benchmark/cpp-httplib-v18/httplib.h @@ -0,0 +1,10154 @@ +// +// httplib.h +// +// Copyright (c) 2025 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.18.0" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +namespace case_ignore { + +inline unsigned char to_lower(int c) { + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return table[(unsigned char)(char)c]; +} + +inline bool equal(const std::string &a, const std::string &b) { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), + [](char ca, char cb) { return to_lower(ca) == to_lower(cb); }); +} + +struct equal_to { + bool operator()(const std::string &a, const std::string &b) const { + return equal(a, b); + } +}; + +struct hash { + size_t operator()(const std::string &key) const { + return hash_core(key.data(), key.size(), 0); + } + + size_t hash_core(const char *s, size_t l, size_t h) const { + return (l == 0) ? h + : hash_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(to_lower(*s))); + } +}; + +}; // namespace case_ignore + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +enum StatusCode { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, +}; + +using Headers = + std::unordered_multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf final : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + std::unordered_map path_params; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = StatusCode::Found_302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + void set_content(std::string &&s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_file_content(const std::string &path, + const std::string &content_type); + void set_file_content(const std::string &path); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool final : public TaskQueue { +public: + explicit ThreadPool(size_t n, size_t mqr = 0) + : shutdown_(false), max_queued_requests_(mqr) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + bool enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) { + return false; + } + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + return true; + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) + OPENSSL_thread_stop(); +#endif + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + size_t max_queued_requests_ = 0; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +const char *status_message(int status); + +std::string get_bearer_token_auth(const Request &req); + +namespace detail { + +class MatcherBase { +public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched agains the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher final : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher final : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) : regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +} // namespace detail + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + template + Server &set_error_handler(ErrorHandlerFunc &&handler) { + return set_error_handler_core( + std::forward(handler), + std::is_convertible{}); + } + + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_ipv6_v6only(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server & + set_header_writer(std::function const &writer); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + void decommission(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); + + Server &set_error_handler_core(HandlerWithResponse handler, std::true_type); + Server &set_error_handler_core(Handler handler, std::false_type); + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, + const Handlers &handlers) const; + bool dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const; + + bool parse_request_line(const char *s, Request &req) const; + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary) const; + bool write_response(Stream &strm, bool close_connection, Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + std::atomic is_running_{false}; + std::atomic is_decommisioned{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + Logger logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + const char *def = "", + size_t id = 0) const; + uint64_t get_request_header_value_u64(const std::string &key, + uint64_t def = 0, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket) const; + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error) const; + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + std::function server_certificate_verifier_; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, + Response &res) const; + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Progress progress); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const; + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + Client &operator=(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + + void update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key, + const std::string &private_key_password = std::string()); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +inline uint64_t get_header_value_u64(const Headers &headers, + const std::string &key, uint64_t def, + size_t id) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +inline uint64_t Request::get_header_value_u64(const std::string &key, + uint64_t def, size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +inline uint64_t Response::get_header_value_u64(const std::string &key, + uint64_t def, size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +inline void default_socket_options(socket_t sock) { + int opt = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&opt), sizeof(opt)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); +#endif +#endif +} + +inline const char *status_message(int status) { + switch (status) { + case StatusCode::Continue_100: return "Continue"; + case StatusCode::SwitchingProtocol_101: return "Switching Protocol"; + case StatusCode::Processing_102: return "Processing"; + case StatusCode::EarlyHints_103: return "Early Hints"; + case StatusCode::OK_200: return "OK"; + case StatusCode::Created_201: return "Created"; + case StatusCode::Accepted_202: return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: return "No Content"; + case StatusCode::ResetContent_205: return "Reset Content"; + case StatusCode::PartialContent_206: return "Partial Content"; + case StatusCode::MultiStatus_207: return "Multi-Status"; + case StatusCode::AlreadyReported_208: return "Already Reported"; + case StatusCode::IMUsed_226: return "IM Used"; + case StatusCode::MultipleChoices_300: return "Multiple Choices"; + case StatusCode::MovedPermanently_301: return "Moved Permanently"; + case StatusCode::Found_302: return "Found"; + case StatusCode::SeeOther_303: return "See Other"; + case StatusCode::NotModified_304: return "Not Modified"; + case StatusCode::UseProxy_305: return "Use Proxy"; + case StatusCode::unused_306: return "unused"; + case StatusCode::TemporaryRedirect_307: return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: return "Permanent Redirect"; + case StatusCode::BadRequest_400: return "Bad Request"; + case StatusCode::Unauthorized_401: return "Unauthorized"; + case StatusCode::PaymentRequired_402: return "Payment Required"; + case StatusCode::Forbidden_403: return "Forbidden"; + case StatusCode::NotFound_404: return "Not Found"; + case StatusCode::MethodNotAllowed_405: return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: return "Request Timeout"; + case StatusCode::Conflict_409: return "Conflict"; + case StatusCode::Gone_410: return "Gone"; + case StatusCode::LengthRequired_411: return "Length Required"; + case StatusCode::PreconditionFailed_412: return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: return "Payload Too Large"; + case StatusCode::UriTooLong_414: return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: return "Expectation Failed"; + case StatusCode::ImATeapot_418: return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: return "Unprocessable Content"; + case StatusCode::Locked_423: return "Locked"; + case StatusCode::FailedDependency_424: return "Failed Dependency"; + case StatusCode::TooEarly_425: return "Too Early"; + case StatusCode::UpgradeRequired_426: return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: return "Precondition Required"; + case StatusCode::TooManyRequests_429: return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: return "Not Implemented"; + case StatusCode::BadGateway_502: return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: return "Insufficient Storage"; + case StatusCode::LoopDetected_508: return "Loop Detected"; + case StatusCode::NotExtended_510: return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: return "Internal Server Error"; + } +} + +inline std::string get_bearer_token_auth(const Request &req) { + if (req.has_header("Authorization")) { + static std::string BearerHeaderPrefix = "Bearer "; + return req.get_header_value("Authorization") + .substr(BearerHeaderPrefix.length()); + } + return ""; +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::ProxyConnection: return "Proxy connection failed"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline uint64_t Result::get_request_header_value_u64(const std::string &key, + uint64_t def, + size_t id) const { + return detail::get_header_value_u64(request_headers_, key, def, id); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(const Ranges &ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +struct FileStat { + FileStat(const std::string &path); + bool is_file() const; + bool is_dir() const; + +private: + struct stat st_; + int ret_ = -1; +}; + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void divide( + const char *data, std::size_t size, char d, + std::function + fn); + +void divide( + const std::string &str, char d, + std::function + fn); + +void split(const char *b, const char *e, char d, + std::function fn); + +void split(const char *b, const char *e, char d, size_t m, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket(const std::string &host, const std::string &ip, + int port, int address_family, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + const char *def, size_t id); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const char *data, std::size_t size, Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream final : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor final : public compressor { +public: + ~nocompressor() override = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; +#else + int fd_ = -1; +#endif + size_t size_ = 0; + void *addr_ = nullptr; + bool is_open_empty_file = false; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = static_cast(code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + if (path[i] == '\0') { + return false; + } else if (path[i] == '\\') { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline FileStat::FileStat(const std::string &path) { + ret_ = stat(path.c_str(), &st_); +} +inline bool FileStat::is_file() const { + return ret_ >= 0 && S_ISREG(st_.st_mode); +} +inline bool FileStat::is_dir() const { + return ret_ >= 0 && S_ISDIR(st_.st_mode); +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + auto val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + auto val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void +divide(const char *data, std::size_t size, char d, + std::function + fn) { + const auto it = std::find(data, data + size, d); + const auto found = static_cast(it != data + size); + const auto lhs_data = data; + const auto lhs_size = static_cast(it - data); + const auto rhs_data = it + found; + const auto rhs_size = size - lhs_size - found; + + fn(lhs_data, lhs_size, rhs_data, rhs_size); +} + +inline void +divide(const std::string &str, char d, + std::function + fn) { + divide(str.data(), str.size(), d, std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, size_t m, + std::function fn) { + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + if (byte == '\n') { break; } +#else + if (prev_byte == '\r' && byte == '\n') { break; } + prev_byte = byte; +#endif + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline mmap::mmap(const char *path) { + open(path); +} + +inline mmap::~mmap() { close(); } + +inline bool mmap::open(const char *path) { + close(); + +#if defined(_WIN32) + std::wstring wpath; + for (size_t i = 0; i < strlen(path); i++) { + wpath += path[i]; + } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, + OPEN_EXISTING, NULL); +#else + hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { return false; } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > + (std::numeric_limits::max)()) { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } + size_ = static_cast(size.QuadPart); + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hMapping_ = + ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); +#else + hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); +#endif + + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) { + close(); + is_open_empty_file = true; + return true; + } + + if (hMapping_ == NULL) { + close(); + return false; + } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); +#else + addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); +#endif + + if (addr_ == nullptr) { + close(); + return false; + } +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); + + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { + close(); + is_open_empty_file = true; + return false; + } +#endif + + return true; +} + +inline bool mmap::is_open() const { + return is_open_empty_file ? true : addr_ != nullptr; +} + +inline size_t mmap::size() const { return size_; } + +inline const char *mmap::data() const { + return is_open_empty_file ? "" : static_cast(addr_); +} + +inline void mmap::close() { +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } + + is_open_empty_file = false; +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream final : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024l * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream final : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + select_read(sock, keep_alive_timeout_sec, 0) > 0) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +inline std::string escape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '\0') { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; +} + +inline std::string +unescape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '@') { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } + +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, + hints.ai_protocol); +#else + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + +#ifndef SOCK_CLOEXEC + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif + + if (socket_options) { socket_options(sock); } + + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + +#ifdef SOCK_CLOEXEC + auto sock = + socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + +#endif + if (sock == INVALID_SOCKET) { continue; } + +#if !defined _WIN32 && !defined SOCK_CLOEXEC + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + auto opt = 1; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (rp->ai_family == AF_INET6) { + auto opt = ipv6_v6only ? 1 : 0; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (socket_options) { socket_options(sock); } + + // bind or connect + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { return sock; } + + close_socket(sock); + + if (quit) { break; } + } + + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, bool ipv6_v6only, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { + if (error == Error::ConnectionTimeout) { quit = true; } + return false; + } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string +find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return default_content_type; + + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + case "text/event-stream"_t: return false; + + default: return !content_type.rfind("text/", 0); + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { return false; } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, const char *def, + size_t id) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key_len = key_end - beg; + if (!key_len) { return false; } + + auto key = std::string(beg, key_end); + auto val = case_ignore::equal(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + + // NOTE: From RFC 9110: + // Field values containing CR, LF, or NUL characters are + // invalid and dangerous, due to the varying ways that + // implementations might parse and interpret those + // characters; a recipient of CR, LF, or NUL within a field + // value MUST either reject the message or replace each of + // those characters with SP before further processing or + // forwarding of that message. + static const std::string CR_LF_NUL("\r\n\0", 3); + if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; } + + fn(key, val); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } else { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; +#else + continue; // Skip invalid line. +#endif + } + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_reader.ptr(), end, + [&](const std::string &key, std::string &val) { + headers.emplace(key, val); + })) { + return false; + } + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n <= 0) { return true; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // does't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-htpplib now allows + // chuncked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } + + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](const std::string &key, const std::string &val) { + x.headers.emplace(key, val); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return case_ignore::equal( + get_header_value(headers, "Transfer-Encoding", "", 0), "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { + status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413 + : StatusCode::BadRequest_400; + } + return ret; + }); +} + +inline ssize_t write_request_line(Stream &strm, const std::string &method, + const std::string &path) { + std::string s = method; + s += " "; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_response_line(Stream &strm, int status) { + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += " "; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && + (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + + if (res.location.empty()) { res.location = location; } + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const char *data, std::size_t size, + Params ¶ms) { + std::set cache; + split(data, data + size, '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(std::move(kv)); + + std::string key; + std::string val; + divide(b, static_cast(e - b), '=', + [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, + std::size_t rhs_size) { + key.assign(lhs_data, lhs_size); + val.assign(rhs_data, rhs_size); + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + parse_query_text(s.data(), s.size(), params); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + auto is_valid = [](const std::string &str) { + return std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); + }; + + if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) { + const auto pos = static_cast(6); + const auto len = static_cast(s.size() - 6); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) { return; } + + const auto it = std::find(b, e, '-'); + if (it == e) { + all_valid_ranges = false; + return; + } + + const auto lhs = std::string(b, it); + const auto rhs = std::string(it + 1, e); + if (!is_valid(lhs) || !is_valid(rhs)) { + all_valid_ranges = false; + return; + } + + const auto first = + static_cast(lhs.empty() ? -1 : std::stoll(lhs)); + const auto last = + static_cast(rhs.empty() ? -1 : std::stoll(rhs)); + if ((first == -1 && last == -1) || + (first != -1 && last != -1 && first > last)) { + all_valid_ranges = false; + return; + } + + ranges.emplace_back(first, last); + }); + return all_valid_ranges && !ranges.empty(); + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), + [&](const std::string &, const std::string &) {})) { + is_valid_ = false; + return false; + } + + static const std::string header_content_type = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) { + file_.content_type = + trim_copy(header.substr(header_content_type.size())); + } else { + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) { + // Only allow UTF-8 enconnding... + static const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { + file_.filename = decode_url(m2[1], false); // override... + } else { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { + return false; + } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string random_string(size_t length) { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + static std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), + seed_gen()}; + + static std::mt19937 engine(seed_sequence); + + std::string result; + for (size_t i = 0; i < length; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; +} + +inline std::string make_multipart_data_boundary() { + return "--cpp-httplib-multipart-data-" + detail::random_string(16); +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { body += serialize_multipart_formdata_finish(boundary); } + + return body; +} + +inline bool range_error(Request &req, Response &res) { + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + ssize_t contant_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + ssize_t prev_first_pos = -1; + ssize_t prev_last_pos = -1; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; } + + for (auto &r : req.ranges) { + auto &first_pos = r.first; + auto &last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = contant_len; + } + + if (first_pos == -1) { + first_pos = contant_len - last_pos; + last_pos = contant_len - 1; + } + + if (last_pos == -1) { last_pos = contant_len - 1; } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= contant_len - 1)) { + return true; + } + + // Ranges must be in ascending order + if (first_pos <= prev_first_pos) { return true; } + + // Request must not have more than two overlapping ranges + if (first_pos <= prev_last_pos) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + } + + prev_first_pos = (std::max)(prev_first_pos, first_pos); + prev_last_pos = (std::max)(prev_last_pos, last_pos); + } + } + + return false; +} + +inline std::pair +get_range_offset_and_length(Range r, size_t content_length) { + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && + r.second < static_cast(content_length)); + (void)(content_length); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field( + const std::pair &offset_and_length, size_t content_length) { + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += "-"; + field += std::to_string(ed); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length, SToken stoken, + CToken ctoken, Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = + get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline void make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + std::string &data) { + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); +} + +inline size_t get_multipart_ranges_data_length(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool +write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, const T &is_shutting_down) { + return process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair +make_range_header(const Ranges &ranges) { + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + const char *def, size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = StatusCode::Found_302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content(std::string &&s, + const std::string &content_type) { + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; +} + +inline void Response::set_file_content(const std::string &path, + const std::string &content_type) { + file_content_path_ = path; + file_content_content_type_ = content_type; +} + +inline void Response::set_file_content(const std::string &path) { + file_content_path_ = path; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(request_headers_, key, def, id); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() = default; + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { + static constexpr char marker[] = "/:"; + + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find( + marker, last_param_end == 0 ? last_param_end : last_param_end - 1); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end + 1)); + + const auto param_name_start = marker_pos + 2; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everything up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() = default; + +inline std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + detail::FileStat stat(dir); + if (stat.is_dir()) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) { + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(HandlerWithResponse handler, + std::true_type) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(Handler handler, + std::false_type) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_ipv6_v6only(bool on) { + ipv6_v6only_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer( + std::function const &writer) { + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret >= 0; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret; +} + +inline bool Server::listen_after_bind() { return listen_internal(); } + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running_ && !is_decommisioned) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + is_decommisioned = false; +} + +inline void Server::decommission() { is_decommisioned = true; } + +inline bool Server::parse_request_line(const char *s, Request &req) const { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + detail::divide(req.target, '?', + [&](const char *lhs_data, std::size_t lhs_size, + const char *rhs_data, std::size_t rhs_size) { + req.path = detail::decode_url( + std::string(lhs_data, lhs_size), false); + detail::parse_query_text(rhs_data, rhs_size, req.params); + }); + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + Request &req, Response &res) { + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); + } + + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && + !res.has_header("Content-Type")) { + res.set_header("Content-Type", "text/plain"); + } + + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && + !res.has_header("Content-Length")) { + res.set_header("Content-Length", "0"); + } + + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { return false; } + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, + offset_and_length.first, + offset_and_length.second, is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, res.content_length_, + is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool +Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = StatusCode::BadRequest_400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = StatusCode::BadRequest_400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + detail::FileStat stat(path); + + if (stat.is_dir()) { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { return false; } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + ipv6_v6only_, std::move(socket_options), + [](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (is_decommisioned) { return -1; } + + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + if (is_decommisioned) { return false; } + + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + +#if defined _WIN32 + // sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else + socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + if (!task_queue->enqueue( + [this, sock]() { process_and_close_socket(sock); })) { + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + } + + task_queue->shutdown(); + } + + is_decommisioned = !ret; + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + auto is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = StatusCode::BadRequest_400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) const { + if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length( + req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + ; + } else if (req.ranges.size() == 1) { + auto offset_and_length = + detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } else { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, + res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + req.remote_addr = remote_addr; + req.remote_port = remote_port; + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + req.local_addr = local_addr; + req.local_port = local_port; + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + detail::write_response_line(strm, status); + strm.write("\r\n"); + break; + default: + connection_closed = true; + return write_response(strm, true, req, res); + } + } + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + if (routed) { + if (res.status == -1) { + res.status = req.ranges.empty() ? StatusCode::OK_200 + : StatusCode::PartialContent_206; + } + + if (detail::range_error(req, res)) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + + // Serve file content by using a content provider + if (!res.file_content_path_.empty()) { + const auto &path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + return write_response(strm, close_connection, req, res); + } + + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + } + + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = StatusCode::NotFound_404; } + + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), + host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + server_certificate_verifier_ = rhs.server_certificate_verifier_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + ipv6_v6only_, socket_options_, connection_timeout_sec_, + connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) const { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) const { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == StatusCode::Continue_100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == StatusCode::Unauthorized_401 || + res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host, next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host, next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) const { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + detail::write_request_line(bstrm, req.method, path); + + header_writer_(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Progress progress) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + req.progress = progress; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && + req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + if (res.has_header("Content-Length")) { + if (!req.content_receiver) { + auto len = std::min(res.get_header_value_u64("Content-Length"), + res.body.max_size()); + if (len > 0) { res.body.reserve(len); } + } + } + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const { + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && !items.empty()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) { + return false; + } + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return Post(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded", + progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return Put(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded", + progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return Patch(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return Patch(path, headers, body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Patch(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Patch(path, headers, body, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return Delete(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, headers, body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.progress = progress; + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Delete(path, Headers(), body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Delete(path, headers, body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline std::string ClientImpl::host() const { return host_; } + +inline int ClientImpl::port() const { return port_; } + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_header_writer( + std::function const &writer) { + header_writer_ = writer; +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} + +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) const { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = detail::scope_exit([&] { BIO_free_all(mem); }); + if (!mem) { return nullptr; } + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { return nullptr; } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return cts; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} + +inline void ClientImpl::enable_server_hostname_verification(bool enabled) { + server_hostname_verification_ = enabled; +} + +inline void ClientImpl::set_server_certificate_verifier( + std::function verifier) { + server_certificate_verifier_ = verifier; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { +#ifdef _WIN32 + SSL_shutdown(ssl); +#else + timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); + + auto ret = SSL_shutdown(ssl); + while (ret == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + ret = SSL_shutdown(ssl); + } +#endif + } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() = default; + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1 || + SSL_CTX_check_private_key(ctx_) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + + std::lock_guard guard(ctx_mutex_); + + SSL_CTX_use_certificate(ctx_, cert); + SSL_CTX_use_PrivateKey(ctx_, private_key); + + if (client_ca_cert_store != nullptr) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + } +} + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, + connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key, + const std::string &private_key_password) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + if (server_certificate_verifier_) { + if (!server_certificate_verifier_(ssl2)) { + error = Error::SSLServerVerification; + return false; + } + } else { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + auto se = detail::scope_exit([&] { X509_free(server_cert); }); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (server_hostname_verification_) { + if (!verify_host(server_cert)) { + error = Error::SSLServerHostnameVerification; + return false; + } + } + } + } + + return true; + }, + [&](SSL *ssl2) { +#if defined(OPENSSL_IS_BORINGSSL) + SSL_set_tlsext_host_name(ssl2, host_.c_str()); +#else + // NOTE: Direct call instead of using the OpenSSL macro to suppress + // -Wold-style-cast warning + SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, + static_cast(const_cast(host_.c_str()))); +#endif + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock, + shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6 {}; + struct in_addr addr {}; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(b, e); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) + // if port param below changes. + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} // namespace detail + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() = default; + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress) { + return cli_->Post(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Post(path, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Post(path, headers, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + return cli_->Post(path, headers, params, progress); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress) { + return cli_->Put(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Put(path, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Put(path, headers, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + return cli_->Put(path, headers, params, progress); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, body, content_length, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, headers, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, body, content_length, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, body, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, headers, body, content_type, progress); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline void Client::stop() { cli_->stop(); } + +inline std::string Client::host() const { return cli_->host(); } + +inline int Client::port() const { return cli_->port(); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_header_writer( + std::function const &writer) { + cli_->set_header_writer(writer); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} + +inline void Client::enable_server_hostname_verification(bool enabled) { + cli_->enable_server_hostname_verification(enabled); +} + +inline void Client::set_server_certificate_verifier( + std::function verifier) { + cli_->set_server_certificate_verifier(verifier); +} +#endif + +inline void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/benchmark/cpp-httplib-v18/main.cpp b/benchmark/cpp-httplib-v18/main.cpp new file mode 100644 index 0000000..86070a1 --- /dev/null +++ b/benchmark/cpp-httplib-v18/main.cpp @@ -0,0 +1,12 @@ +#include "./httplib.h" +using namespace httplib; + +int main() { + Server svr; + + svr.Get("/", [](const Request &, Response &res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.listen("0.0.0.0", 8080); +} diff --git a/benchmark/cpp-httplib-v19/httplib.h b/benchmark/cpp-httplib-v19/httplib.h new file mode 100644 index 0000000..e4799da --- /dev/null +++ b/benchmark/cpp-httplib-v19/httplib.h @@ -0,0 +1,10475 @@ +// +// httplib.h +// +// Copyright (c) 2025 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.19.0" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND +#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#ifndef __VMS +#include +#endif +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +namespace case_ignore { + +inline unsigned char to_lower(int c) { + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return table[(unsigned char)(char)c]; +} + +inline bool equal(const std::string &a, const std::string &b) { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { + return to_lower(ca) == to_lower(cb); + }); +} + +struct equal_to { + bool operator()(const std::string &a, const std::string &b) const { + return equal(a, b); + } +}; + +struct hash { + size_t operator()(const std::string &key) const { + return hash_core(key.data(), key.size(), 0); + } + + size_t hash_core(const char *s, size_t l, size_t h) const { + return (l == 0) ? h + : hash_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(to_lower(*s))); + } +}; + +} // namespace case_ignore + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +enum StatusCode { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, +}; + +using Headers = + std::unordered_multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf final : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Params params; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + std::unordered_map path_params; + std::function is_connection_closed = []() { return true; }; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; + std::chrono::time_point start_time_ = + std::chrono::steady_clock::time_point::min(); +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = StatusCode::Found_302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + void set_content(std::string &&s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_file_content(const std::string &path, + const std::string &content_type); + void set_file_content(const std::string &path); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + virtual time_t duration() const = 0; + + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool final : public TaskQueue { +public: + explicit ThreadPool(size_t n, size_t mqr = 0) + : shutdown_(false), max_queued_requests_(mqr) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + bool enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) { + return false; + } + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + return true; + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) + OPENSSL_thread_stop(); +#endif + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + size_t max_queued_requests_ = 0; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +const char *status_message(int status); + +std::string get_bearer_token_auth(const Request &req); + +namespace detail { + +class MatcherBase { +public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched agains the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher final : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher final : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) : regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +} // namespace detail + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + template + Server &set_error_handler(ErrorHandlerFunc &&handler) { + return set_error_handler_core( + std::forward(handler), + std::is_convertible{}); + } + + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_ipv6_v6only(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server & + set_header_writer(std::function const &writer); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + void decommission(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); + + Server &set_error_handler_core(HandlerWithResponse handler, std::true_type); + Server &set_error_handler_core(Handler handler, std::false_type); + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, + const Handlers &handlers) const; + bool dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const; + + bool parse_request_line(const char *s, Request &req) const; + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary) const; + bool write_response(Stream &strm, bool close_connection, Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + std::atomic is_running_{false}; + std::atomic is_decommisioned{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + Logger logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + const char *def = "", + size_t id = 0) const; + uint64_t get_request_header_value_u64(const std::string &key, + uint64_t def = 0, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket) const; + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error) const; + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + std::function server_certificate_verifier_; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, + Response &res) const; + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Progress progress); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const; + + std::string adjust_host_string(const std::string &host) const; + + virtual bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + Client &operator=(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type, + Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type, + Progress progress); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + + void update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key, + const std::string &private_key_password = std::string()); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy( + Socket &sock, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +inline bool is_numeric(const std::string &str) { + return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); +} + +inline uint64_t get_header_value_u64(const Headers &headers, + const std::string &key, uint64_t def, + size_t id, bool &is_invalid_value) { + is_invalid_value = false; + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + if (is_numeric(it->second)) { + return std::strtoull(it->second.data(), nullptr, 10); + } else { + is_invalid_value = true; + } + } + return def; +} + +inline uint64_t get_header_value_u64(const Headers &headers, + const std::string &key, uint64_t def, + size_t id) { + bool dummy = false; + return get_header_value_u64(headers, key, def, id, dummy); +} + +} // namespace detail + +inline uint64_t Request::get_header_value_u64(const std::string &key, + uint64_t def, size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +inline uint64_t Response::get_header_value_u64(const std::string &key, + uint64_t def, size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +inline void default_socket_options(socket_t sock) { + int opt = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); +#endif +#endif +} + +inline const char *status_message(int status) { + switch (status) { + case StatusCode::Continue_100: return "Continue"; + case StatusCode::SwitchingProtocol_101: return "Switching Protocol"; + case StatusCode::Processing_102: return "Processing"; + case StatusCode::EarlyHints_103: return "Early Hints"; + case StatusCode::OK_200: return "OK"; + case StatusCode::Created_201: return "Created"; + case StatusCode::Accepted_202: return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: return "No Content"; + case StatusCode::ResetContent_205: return "Reset Content"; + case StatusCode::PartialContent_206: return "Partial Content"; + case StatusCode::MultiStatus_207: return "Multi-Status"; + case StatusCode::AlreadyReported_208: return "Already Reported"; + case StatusCode::IMUsed_226: return "IM Used"; + case StatusCode::MultipleChoices_300: return "Multiple Choices"; + case StatusCode::MovedPermanently_301: return "Moved Permanently"; + case StatusCode::Found_302: return "Found"; + case StatusCode::SeeOther_303: return "See Other"; + case StatusCode::NotModified_304: return "Not Modified"; + case StatusCode::UseProxy_305: return "Use Proxy"; + case StatusCode::unused_306: return "unused"; + case StatusCode::TemporaryRedirect_307: return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: return "Permanent Redirect"; + case StatusCode::BadRequest_400: return "Bad Request"; + case StatusCode::Unauthorized_401: return "Unauthorized"; + case StatusCode::PaymentRequired_402: return "Payment Required"; + case StatusCode::Forbidden_403: return "Forbidden"; + case StatusCode::NotFound_404: return "Not Found"; + case StatusCode::MethodNotAllowed_405: return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: return "Request Timeout"; + case StatusCode::Conflict_409: return "Conflict"; + case StatusCode::Gone_410: return "Gone"; + case StatusCode::LengthRequired_411: return "Length Required"; + case StatusCode::PreconditionFailed_412: return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: return "Payload Too Large"; + case StatusCode::UriTooLong_414: return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: return "Expectation Failed"; + case StatusCode::ImATeapot_418: return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: return "Unprocessable Content"; + case StatusCode::Locked_423: return "Locked"; + case StatusCode::FailedDependency_424: return "Failed Dependency"; + case StatusCode::TooEarly_425: return "Too Early"; + case StatusCode::UpgradeRequired_426: return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: return "Precondition Required"; + case StatusCode::TooManyRequests_429: return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: return "Not Implemented"; + case StatusCode::BadGateway_502: return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: return "Insufficient Storage"; + case StatusCode::LoopDetected_508: return "Loop Detected"; + case StatusCode::NotExtended_510: return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: return "Internal Server Error"; + } +} + +inline std::string get_bearer_token_auth(const Request &req) { + if (req.has_header("Authorization")) { + static std::string BearerHeaderPrefix = "Bearer "; + return req.get_header_value("Authorization") + .substr(BearerHeaderPrefix.length()); + } + return ""; +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::ProxyConnection: return "Proxy connection failed"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline uint64_t Result::get_request_header_value_u64(const std::string &key, + uint64_t def, + size_t id) const { + return detail::get_header_value_u64(request_headers_, key, def, id); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_max_timeout( + const std::chrono::duration &duration) { + auto msec = + std::chrono::duration_cast(duration).count(); + set_max_timeout(msec); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +template +inline void +Client::set_max_timeout(const std::chrono::duration &duration) { + cli_->set_max_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(const Ranges &ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +#if defined(_WIN32) +inline std::wstring u8string_to_wstring(const char *s) { + std::wstring ws; + auto len = static_cast(strlen(s)); + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (wlen > 0) { + ws.resize(wlen); + wlen = ::MultiByteToWideChar( + CP_UTF8, 0, s, len, + const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { ws.clear(); } + } + return ws; +} +#endif + +struct FileStat { + FileStat(const std::string &path); + bool is_file() const; + bool is_dir() const; + +private: +#if defined(_WIN32) + struct _stat st_; +#else + struct stat st_; +#endif + int ret_ = -1; +}; + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void divide( + const char *data, std::size_t size, char d, + std::function + fn); + +void divide( + const std::string &str, char d, + std::function + fn); + +void split(const char *b, const char *e, char d, + std::function fn); + +void split(const char *b, const char *e, char d, size_t m, + std::function fn); + +bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback); + +socket_t create_client_socket(const std::string &host, const std::string &ip, + int port, int address_family, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + const char *def, size_t id); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const char *data, std::size_t size, Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream final : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor final : public compressor { +public: + ~nocompressor() override = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; +#else + int fd_ = -1; +#endif + size_t size_ = 0; + void *addr_ = nullptr; + bool is_open_empty_file = false; +}; + +// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 +namespace fields { + +inline bool is_token_char(char c) { + return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || + c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; +} + +inline bool is_token(const std::string &s) { + if (s.empty()) { return false; } + for (auto c : s) { + if (!is_token_char(c)) { return false; } + } + return true; +} + +inline bool is_field_name(const std::string &s) { return is_token(s); } + +inline bool is_vchar(char c) { return c >= 33 && c <= 126; } + +inline bool is_obs_text(char c) { return 128 <= static_cast(c); } + +inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); } + +inline bool is_field_content(const std::string &s) { + if (s.empty()) { return true; } + + if (s.size() == 1) { + return is_field_vchar(s[0]); + } else if (s.size() == 2) { + return is_field_vchar(s[0]) && is_field_vchar(s[1]); + } else { + size_t i = 0; + + if (!is_field_vchar(s[i])) { return false; } + i++; + + while (i < s.size() - 1) { + auto c = s[i++]; + if (c == ' ' || c == '\t' || is_field_vchar(c)) { + } else { + return false; + } + } + + return is_field_vchar(s[i]); + } +} + +inline bool is_field_value(const std::string &s) { return is_field_content(s); } + +} // namespace fields + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = static_cast(code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + if (path[i] == '\0') { + return false; + } else if (path[i] == '\\') { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline FileStat::FileStat(const std::string &path) { +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else + ret_ = stat(path.c_str(), &st_); +#endif +} +inline bool FileStat::is_file() const { + return ret_ >= 0 && S_ISREG(st_.st_mode); +} +inline bool FileStat::is_dir() const { + return ret_ >= 0 && S_ISDIR(st_.st_mode); +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + auto val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + auto val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void +divide(const char *data, std::size_t size, char d, + std::function + fn) { + const auto it = std::find(data, data + size, d); + const auto found = static_cast(it != data + size); + const auto lhs_data = data; + const auto lhs_size = static_cast(it - data); + const auto rhs_data = it + found; + const auto rhs_size = size - lhs_size - found; + + fn(lhs_data, lhs_size, rhs_data, rhs_size); +} + +inline void +divide(const std::string &str, char d, + std::function + fn) { + divide(str.data(), str.size(), d, std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, size_t m, + std::function fn) { + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + if (byte == '\n') { break; } +#else + if (prev_byte == '\r' && byte == '\n') { break; } + prev_byte = byte; +#endif + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline mmap::mmap(const char *path) { open(path); } + +inline mmap::~mmap() { close(); } + +inline bool mmap::open(const char *path) { + close(); + +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { return false; } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, + OPEN_EXISTING, NULL); +#else + hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { return false; } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > + (std::numeric_limits::max)()) { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } + size_ = static_cast(size.QuadPart); + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hMapping_ = + ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); +#else + hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); +#endif + + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) { + close(); + is_open_empty_file = true; + return true; + } + + if (hMapping_ == NULL) { + close(); + return false; + } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); +#else + addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); +#endif + + if (addr_ == nullptr) { + close(); + return false; + } +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); + + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { + close(); + is_open_empty_file = true; + return false; + } +#endif + + return true; +} + +inline bool mmap::is_open() const { + return is_open_empty_file ? true : addr_ != nullptr; +} + +inline size_t mmap::size() const { return size_; } + +inline const char *mmap::data() const { + return is_open_empty_file ? "" : static_cast(addr_); +} + +inline void mmap::close() { +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } + + is_open_empty_file = false; +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +template +inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd; + pfd.fd = sock; + pfd.events = (Read ? POLLIN : POLLOUT); + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds, *rfds, *wfds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + rfds = (Read ? &fds : nullptr); + wfds = (Read ? nullptr : &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), rfds, wfds, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream final : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + std::chrono::steady_clock::time_point::min()); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024l * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream final : public Stream { +public: + SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + std::chrono::steady_clock::time_point::min()); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time; +}; +#endif + +inline bool keep_alive(const std::atomic &svr_sock, socket_t sock, + time_t keep_alive_timeout_sec) { + using namespace std::chrono; + + const auto interval_usec = + CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND; + + // Avoid expensive `steady_clock::now()` call for the first time + if (select_read(sock, 0, interval_usec) > 0) { return true; } + + const auto start = steady_clock::now() - microseconds{interval_usec}; + const auto timeout = seconds{keep_alive_timeout_sec}; + + while (true) { + if (svr_sock == INVALID_SOCKET) { + break; // Server socket is closed + } + + auto val = select_read(sock, 0, interval_usec); + if (val < 0) { + break; // Ssocket error + } else if (val == 0) { + if (steady_clock::now() - start > timeout) { + break; // Timeout + } + } else { + return true; // Ready for read + } + } + + return false; +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +inline std::string escape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '\0') { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; +} + +inline std::string +unescape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '@') { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } + +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, + hints.ai_protocol); +#else + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + +#ifndef SOCK_CLOEXEC + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif + + if (socket_options) { socket_options(sock); } + + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + +#ifdef SOCK_CLOEXEC + auto sock = + socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + +#endif + if (sock == INVALID_SOCKET) { continue; } + +#if !defined _WIN32 && !defined SOCK_CLOEXEC + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + auto opt = 1; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (rp->ai_family == AF_INET6) { + auto opt = ipv6_v6only ? 1 : 0; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (socket_options) { socket_options(sock); } + + // bind or connect + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { return sock; } + + close_socket(sock); + + if (quit) { break; } + } + + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, bool ipv6_v6only, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { + if (error == Error::ConnectionTimeout) { quit = true; } + return false; + } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator""_t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string +find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return default_content_type; + + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + case "text/event-stream"_t: return false; + + default: return !content_type.rfind("text/", 0); + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { return false; } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, const char *def, + size_t id) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p <= end) { + auto key_len = key_end - beg; + if (!key_len) { return false; } + + auto key = std::string(beg, key_end); + // auto val = (case_ignore::equal(key, "Location") || + // case_ignore::equal(key, "Referer")) + // ? std::string(p, end) + // : decode_url(std::string(p, end), false); + auto val = std::string(p, end); + + if (!detail::fields::is_field_value(val)) { return false; } + + if (case_ignore::equal(key, "Location") || + case_ignore::equal(key, "Referer")) { + fn(key, val); + } else { + fn(key, decode_url(val, false)); + } + + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } else { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; +#else + continue; // Skip invalid line. +#endif + } + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_reader.ptr(), end, + [&](const std::string &key, const std::string &val) { + headers.emplace(key, val); + })) { + return false; + } + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // does't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-htpplib now allows + // chuncked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } + + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](const std::string &key, const std::string &val) { + x.headers.emplace(key, val); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return case_ignore::equal( + get_header_value(headers, "Transfer-Encoding", "", 0), "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto is_invalid_value = false; + auto len = get_header_value_u64( + x.headers, "Content-Length", + (std::numeric_limits::max)(), 0, is_invalid_value); + + if (is_invalid_value) { + ret = false; + } else if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { + status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413 + : StatusCode::BadRequest_400; + } + return ret; + }); +} + +inline ssize_t write_request_line(Stream &strm, const std::string &method, + const std::string &path) { + std::string s = method; + s += " "; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_response_line(Stream &strm, int status) { + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += " "; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && + (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + + if (res.location.empty()) { res.location = location; } + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const char *data, std::size_t size, + Params ¶ms) { + std::set cache; + split(data, data + size, '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(std::move(kv)); + + std::string key; + std::string val; + divide(b, static_cast(e - b), '=', + [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, + std::size_t rhs_size) { + key.assign(lhs_data, lhs_size); + val.assign(rhs_data, rhs_size); + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + parse_query_text(s.data(), s.size(), params); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + auto is_valid = [](const std::string &str) { + return std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); + }; + + if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) { + const auto pos = static_cast(6); + const auto len = static_cast(s.size() - 6); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) { return; } + + const auto it = std::find(b, e, '-'); + if (it == e) { + all_valid_ranges = false; + return; + } + + const auto lhs = std::string(b, it); + const auto rhs = std::string(it + 1, e); + if (!is_valid(lhs) || !is_valid(rhs)) { + all_valid_ranges = false; + return; + } + + const auto first = + static_cast(lhs.empty() ? -1 : std::stoll(lhs)); + const auto last = + static_cast(rhs.empty() ? -1 : std::stoll(rhs)); + if ((first == -1 && last == -1) || + (first != -1 && last != -1 && first > last)) { + all_valid_ranges = false; + return; + } + + ranges.emplace_back(first, last); + }); + return all_valid_ranges && !ranges.empty(); + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), + [&](const std::string &, const std::string &) {})) { + is_valid_ = false; + return false; + } + + static const std::string header_content_type = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) { + file_.content_type = + trim_copy(header.substr(header_content_type.size())); + } else { + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) { + // Only allow UTF-8 enconnding... + static const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { + file_.filename = decode_url(m2[1], false); // override... + } else { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { + return false; + } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string random_string(size_t length) { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + static std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), + seed_gen()}; + + static std::mt19937 engine(seed_sequence); + + std::string result; + for (size_t i = 0; i < length; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; +} + +inline std::string make_multipart_data_boundary() { + return "--cpp-httplib-multipart-data-" + detail::random_string(16); +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { body += serialize_multipart_formdata_finish(boundary); } + + return body; +} + +inline bool range_error(Request &req, Response &res) { + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + ssize_t contant_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + ssize_t prev_first_pos = -1; + ssize_t prev_last_pos = -1; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; } + + for (auto &r : req.ranges) { + auto &first_pos = r.first; + auto &last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = contant_len; + } + + if (first_pos == -1) { + first_pos = contant_len - last_pos; + last_pos = contant_len - 1; + } + + // NOTE: RFC-9110 '14.1.2. Byte Ranges': + // A client can limit the number of bytes requested without knowing the + // size of the selected representation. If the last-pos value is absent, + // or if the value is greater than or equal to the current length of the + // representation data, the byte range is interpreted as the remainder of + // the representation (i.e., the server replaces the value of last-pos + // with a value that is one less than the current length of the selected + // representation). + // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 + if (last_pos == -1 || last_pos >= contant_len) { + last_pos = contant_len - 1; + } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= contant_len - 1)) { + return true; + } + + // Ranges must be in ascending order + if (first_pos <= prev_first_pos) { return true; } + + // Request must not have more than two overlapping ranges + if (first_pos <= prev_last_pos) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + } + + prev_first_pos = (std::max)(prev_first_pos, first_pos); + prev_last_pos = (std::max)(prev_last_pos, last_pos); + } + } + + return false; +} + +inline std::pair +get_range_offset_and_length(Range r, size_t content_length) { + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && + r.second < static_cast(content_length)); + (void)(content_length); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field( + const std::pair &offset_and_length, size_t content_length) { + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += "-"; + field += std::to_string(ed); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length, SToken stoken, + CToken ctoken, Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = + get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline void make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + std::string &data) { + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); +} + +inline size_t get_multipart_ranges_data_length(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool +write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, const T &is_shutting_down) { + return process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "DELETE") { + return true; + } + if (req.has_header("Content-Length") && + req.get_header_value_u64("Content-Length") > 0) { + return true; + } + if (is_chunked_transfer_encoding(req.headers)) { return true; } + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} + +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} + +inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) { + detail::set_nonblocking(sock, true); + auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + char buf[1]; + return !SSL_peek(ssl, buf, 1) && + SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; +} + +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair +make_range_header(const Ranges &ranges) { + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + const char *def, size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (detail::fields::is_field_value(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = StatusCode::Found_302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content(std::string &&s, + const std::string &content_type) { + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; +} + +inline void Response::set_file_content(const std::string &path, + const std::string &content_type) { + file_content_path_ = path; + file_content_content_type_ = content_type; +} + +inline void Response::set_file_content(const std::string &path) { + file_content_path_ = path; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(request_headers_, key, def, id); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +inline void calc_actual_timeout(time_t max_timeout_msec, + time_t duration_msec, time_t timeout_sec, + time_t timeout_usec, time_t &actual_timeout_sec, + time_t &actual_timeout_usec) { + auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000); + + auto actual_timeout_msec = + std::min(max_timeout_msec - duration_msec, timeout_msec); + + actual_timeout_sec = actual_timeout_msec / 1000; + actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; +} + +// Socket stream implementation +inline SocketStream::SocketStream( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time(start_time), + read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() = default; + +inline bool SocketStream::is_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +inline time_t SocketStream::duration() const { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); +} + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline time_t BufferStream::duration() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { + static constexpr char marker[] = "/:"; + + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find( + marker, last_param_end == 0 ? last_param_end : last_param_end - 1); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end + 1)); + + const auto param_name_start = marker_pos + 2; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everything up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() = default; + +inline std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + detail::FileStat stat(dir); + if (stat.is_dir()) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) { + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(HandlerWithResponse handler, + std::true_type) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(Handler handler, + std::false_type) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_ipv6_v6only(bool on) { + ipv6_v6only_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer( + std::function const &writer) { + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret >= 0; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret; +} + +inline bool Server::listen_after_bind() { return listen_internal(); } + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running_ && !is_decommisioned) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + is_decommisioned = false; +} + +inline void Server::decommission() { is_decommisioned = true; } + +inline bool Server::parse_request_line(const char *s, Request &req) const { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + detail::divide(req.target, '?', + [&](const char *lhs_data, std::size_t lhs_size, + const char *rhs_data, std::size_t rhs_size) { + req.path = detail::decode_url( + std::string(lhs_data, lhs_size), false); + detail::parse_query_text(rhs_data, rhs_size, req.params); + }); + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + Request &req, Response &res) { + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); + } + + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && + !res.has_header("Content-Type")) { + res.set_header("Content-Type", "text/plain"); + } + + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && + !res.has_header("Content-Length")) { + res.set_header("Content-Length", "0"); + } + + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { return false; } + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, + offset_and_length.first, + offset_and_length.second, is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, res.content_length_, + is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool +Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = StatusCode::BadRequest_400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = StatusCode::BadRequest_400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + detail::FileStat stat(path); + + if (stat.is_dir()) { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { return false; } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + ipv6_v6only_, std::move(socket_options), + [](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (is_decommisioned) { return -1; } + + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + if (is_decommisioned) { return false; } + + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + +#if defined _WIN32 + // sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else + socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + if (!task_queue->enqueue( + [this, sock]() { process_and_close_socket(sock); })) { + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + } + + task_queue->shutdown(); + } + + is_decommisioned = !ret; + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + auto is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = StatusCode::BadRequest_400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) const { + if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length( + req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + ; + } else if (req.ranges.size() == 1) { + auto offset_and_length = + detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } else { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, + res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + + // Check if the request URI doesn't exceed the limit + if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + req.remote_addr = remote_addr; + req.remote_port = remote_port; + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + req.local_addr = local_addr; + req.local_port = local_port; + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + detail::write_response_line(strm, status); + strm.write("\r\n"); + break; + default: + connection_closed = true; + return write_response(strm, true, req, res); + } + } + + // Setup `is_connection_closed` method + req.is_connection_closed = [&]() { + return !detail::is_socket_alive(strm.socket()); + }; + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + if (routed) { + if (res.status == -1) { + res.status = req.ranges.empty() ? StatusCode::OK_200 + : StatusCode::PartialContent_206; + } + + // Serve file content by using a content provider + if (!res.file_content_path_.empty()) { + const auto &path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + return write_response(strm, close_connection, req, res); + } + + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + } + + if (detail::range_error(req, res)) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = StatusCode::NotFound_404; } + + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), + host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + max_timeout_msec_ = rhs.max_timeout_msec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + server_certificate_verifier_ = rhs.server_certificate_verifier_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + ipv6_v6only_, socket_options_, connection_timeout_sec_, + connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) const { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) const { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == StatusCode::Continue_100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_alive && is_ssl()) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + is_alive = false; + } + } +#endif + + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, req.start_time_, res, success, + error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, req.start_time_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == StatusCode::Unauthorized_401 || + res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host, next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host, next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) const { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + + if (!req.content_receiver) { + if (!req.has_header("Accept-Encoding")) { + std::string accept_encoding; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + accept_encoding = "br"; +#endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "gzip, deflate"; +#endif + req.set_header("Accept-Encoding", accept_encoding); + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + }; + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path_with_query = + req.params.empty() ? req.path + : append_query_params(req.path, req.params); + + const auto &path = + url_encode_ ? detail::encode_url(path_with_query) : path_with_query; + + detail::write_request_line(bstrm, req.method, path); + + header_writer_(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Progress progress) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + req.progress = progress; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && + req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && + res.status != StatusCode::NotModified_304 && + follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + assert(res.body.size() + n <= res.body.max_size()); + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + if (res.has_header("Content-Length")) { + if (!req.content_receiver) { + auto len = res.get_header_value_u64("Content-Length"); + if (len > res.body.max_size()) { + error = Error::Read; + return false; + } + res.body.reserve(static_cast(len)); + } + } + + if (res.status != StatusCode::NotModified_304) { + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), + std::move(out), decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const { + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && !items.empty()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) { + return false; + } + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool ClientImpl::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, max_timeout_msec_, start_time, + std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return Post(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded", + progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return Put(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded", + progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return Patch(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return Patch(path, headers, body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Patch(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Patch(path, headers, body, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return Delete(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, headers, body, content_length, content_type, nullptr); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.progress = progress; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Delete(path, Headers(), body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return Delete(path, headers, body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline std::string ClientImpl::host() const { return host_; } + +inline int ClientImpl::port() const { return port_; } + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_max_timeout(time_t msec) { + max_timeout_msec_ = msec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_header_writer( + std::function const &writer) { + header_writer_ = writer; +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} + +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) const { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = detail::scope_exit([&] { BIO_free_all(mem); }); + if (!mem) { return nullptr; } + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { return nullptr; } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return cts; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} + +inline void ClientImpl::enable_server_hostname_verification(bool enabled) { + server_hostname_verification_ = enabled; +} + +inline void ClientImpl::set_server_certificate_verifier( + std::function verifier) { + server_certificate_verifier_ = verifier; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { +#ifdef _WIN32 + (void)(sock); + SSL_shutdown(ssl); +#else + timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); + + auto ret = SSL_shutdown(ssl); + while (ret == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + ret = SSL_shutdown(ssl); + } +#endif + } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool process_client_socket_ssl( + SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec, + max_timeout_msec, start_time); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time(start_time) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() = default; + +inline bool SSLSocketStream::is_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } else { + return -1; + } +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +inline time_t SSLSocketStream::duration() const { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); +} + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1 || + SSL_CTX_check_private_key(ctx_) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + + std::lock_guard guard(ctx_mutex_); + + SSL_CTX_use_certificate(ctx_, cert); + SSL_CTX_use_PrivateKey(ctx_, private_key); + + if (client_ca_cert_store != nullptr) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + } +} + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, + connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key, + const std::string &private_key_password) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy( + Socket &socket, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + if (max_timeout_msec_ > 0) { + req2.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + if (max_timeout_msec_ > 0) { + req3.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + if (server_certificate_verifier_) { + if (!server_certificate_verifier_(ssl2)) { + error = Error::SSLServerVerification; + return false; + } + } else { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + auto se = detail::scope_exit([&] { X509_free(server_cert); }); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (server_hostname_verification_) { + if (!verify_host(server_cert)) { + error = Error::SSLServerHostnameVerification; + return false; + } + } + } + } + + return true; + }, + [&](SSL *ssl2) { +#if defined(OPENSSL_IS_BORINGSSL) + SSL_set_tlsext_host_name(ssl2, host_.c_str()); +#else + // NOTE: Direct call instead of using the OpenSSL macro to suppress + // -Wold-style-cast warning + SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, + static_cast(const_cast(host_.c_str()))); +#endif + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock, + shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool SSLClient::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time, + std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6{}; + struct in_addr addr{}; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(b, e); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) + // if port param below changes. + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} // namespace detail + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() = default; + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress) { + return cli_->Post(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Post(path, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Post(path, headers, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + return cli_->Post(path, headers, params, progress); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, Progress progress) { + return cli_->Put(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Put(path, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, Progress progress) { + return cli_->Put(path, headers, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms, Progress progress) { + return cli_->Put(path, headers, params, progress); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, body, content_length, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Patch(path, headers, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, body, content_length, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, body, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + Progress progress) { + return cli_->Delete(path, headers, body, content_type, progress); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline void Client::stop() { cli_->stop(); } + +inline std::string Client::host() const { return cli_->host(); } + +inline int Client::port() const { return cli_->port(); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_header_writer( + std::function const &writer) { + cli_->set_header_writer(writer); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} + +inline void Client::enable_server_hostname_verification(bool enabled) { + cli_->enable_server_hostname_verification(enabled); +} + +inline void Client::set_server_certificate_verifier( + std::function verifier) { + cli_->set_server_certificate_verifier(verifier); +} +#endif + +inline void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/benchmark/cpp-httplib-v19/main.cpp b/benchmark/cpp-httplib-v19/main.cpp new file mode 100644 index 0000000..86070a1 --- /dev/null +++ b/benchmark/cpp-httplib-v19/main.cpp @@ -0,0 +1,12 @@ +#include "./httplib.h" +using namespace httplib; + +int main() { + Server svr; + + svr.Get("/", [](const Request &, Response &res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.listen("0.0.0.0", 8080); +} diff --git a/benchmark/cpp-httplib/main.cpp b/benchmark/cpp-httplib/main.cpp new file mode 100644 index 0000000..ab2e757 --- /dev/null +++ b/benchmark/cpp-httplib/main.cpp @@ -0,0 +1,12 @@ +#include "httplib.h" +using namespace httplib; + +int main() { + Server svr; + + svr.Get("/", [](const Request &, Response &res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.listen("0.0.0.0", 8080); +} diff --git a/benchmark/crow/crow_all.h b/benchmark/crow/crow_all.h new file mode 100644 index 0000000..0465f96 --- /dev/null +++ b/benchmark/crow/crow_all.h @@ -0,0 +1,14316 @@ +// SPDX-License-Identifier: BSD-3-Clause AND ISC AND MIT +/*BSD 3-Clause License + +Copyright (c) 2014-2017, ipkn + 2020-2022, CrowCpp +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The Crow logo and other graphic material (excluding third party logos) used are under exclusive Copyright (c) 2021-2022, Farook Al-Sammarraie (The-EDev), All rights reserved. +*/ +#pragma once +// This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on 2021-12-03. +#include +#include + +namespace crow +{ + const std::unordered_map mime_types{ + {"shtml", "text/html"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"css", "text/css"}, + {"xml", "text/xml"}, + {"gif", "image/gif"}, + {"jpg", "image/jpeg"}, + {"jpeg", "image/jpeg"}, + {"js", "application/javascript"}, + {"atom", "application/atom+xml"}, + {"rss", "application/rss+xml"}, + {"mml", "text/mathml"}, + {"txt", "text/plain"}, + {"jad", "text/vnd.sun.j2me.app-descriptor"}, + {"wml", "text/vnd.wap.wml"}, + {"htc", "text/x-component"}, + {"avif", "image/avif"}, + {"png", "image/png"}, + {"svgz", "image/svg+xml"}, + {"svg", "image/svg+xml"}, + {"tiff", "image/tiff"}, + {"tif", "image/tiff"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"webp", "image/webp"}, + {"ico", "image/x-icon"}, + {"jng", "image/x-jng"}, + {"bmp", "image/x-ms-bmp"}, + {"woff", "font/woff"}, + {"woff2", "font/woff2"}, + {"ear", "application/java-archive"}, + {"war", "application/java-archive"}, + {"jar", "application/java-archive"}, + {"json", "application/json"}, + {"hqx", "application/mac-binhex40"}, + {"doc", "application/msword"}, + {"pdf", "application/pdf"}, + {"ai", "application/postscript"}, + {"eps", "application/postscript"}, + {"ps", "application/postscript"}, + {"rtf", "application/rtf"}, + {"m3u8", "application/vnd.apple.mpegurl"}, + {"kml", "application/vnd.google-earth.kml+xml"}, + {"kmz", "application/vnd.google-earth.kmz"}, + {"xls", "application/vnd.ms-excel"}, + {"eot", "application/vnd.ms-fontobject"}, + {"ppt", "application/vnd.ms-powerpoint"}, + {"odg", "application/vnd.oasis.opendocument.graphics"}, + {"odp", "application/vnd.oasis.opendocument.presentation"}, + {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, + {"odt", "application/vnd.oasis.opendocument.text"}, + {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"wasm", "application/wasm"}, + {"7z", "application/x-7z-compressed"}, + {"cco", "application/x-cocoa"}, + {"jardiff", "application/x-java-archive-diff"}, + {"jnlp", "application/x-java-jnlp-file"}, + {"run", "application/x-makeself"}, + {"pm", "application/x-perl"}, + {"pl", "application/x-perl"}, + {"pdb", "application/x-pilot"}, + {"prc", "application/x-pilot"}, + {"rar", "application/x-rar-compressed"}, + {"rpm", "application/x-redhat-package-manager"}, + {"sea", "application/x-sea"}, + {"swf", "application/x-shockwave-flash"}, + {"sit", "application/x-stuffit"}, + {"tk", "application/x-tcl"}, + {"tcl", "application/x-tcl"}, + {"crt", "application/x-x509-ca-cert"}, + {"pem", "application/x-x509-ca-cert"}, + {"der", "application/x-x509-ca-cert"}, + {"xpi", "application/x-xpinstall"}, + {"xhtml", "application/xhtml+xml"}, + {"xspf", "application/xspf+xml"}, + {"zip", "application/zip"}, + {"dll", "application/octet-stream"}, + {"exe", "application/octet-stream"}, + {"bin", "application/octet-stream"}, + {"deb", "application/octet-stream"}, + {"dmg", "application/octet-stream"}, + {"img", "application/octet-stream"}, + {"iso", "application/octet-stream"}, + {"msm", "application/octet-stream"}, + {"msp", "application/octet-stream"}, + {"msi", "application/octet-stream"}, + {"kar", "audio/midi"}, + {"midi", "audio/midi"}, + {"mid", "audio/midi"}, + {"mp3", "audio/mpeg"}, + {"ogg", "audio/ogg"}, + {"m4a", "audio/x-m4a"}, + {"ra", "audio/x-realaudio"}, + {"3gp", "video/3gpp"}, + {"3gpp", "video/3gpp"}, + {"ts", "video/mp2t"}, + {"mp4", "video/mp4"}, + {"mpg", "video/mpeg"}, + {"mpeg", "video/mpeg"}, + {"mov", "video/quicktime"}, + {"webm", "video/webm"}, + {"flv", "video/x-flv"}, + {"m4v", "video/x-m4v"}, + {"mng", "video/x-mng"}, + {"asf", "video/x-ms-asf"}, + {"asx", "video/x-ms-asf"}, + {"wmv", "video/x-ms-wmv"}, + {"avi", "video/x-msvideo"}}; +} + + +#include + +namespace crow +{ + /// An abstract class that allows any other class to be returned by a handler. + struct returnable + { + std::string content_type; + virtual std::string dump() const = 0; + + returnable(std::string ctype): + content_type{ctype} + {} + + virtual ~returnable(){}; + }; +} // namespace crow + + +#include +#include +#include +#include +#include +#include +#include + +namespace crow +{ + +// ---------------------------------------------------------------------------- +// qs_parse (modified) +// https://github.com/bartgrantham/qs_parse +// ---------------------------------------------------------------------------- +/* Similar to strncmp, but handles URL-encoding for either string */ +int qs_strncmp(const char* s, const char* qs, size_t n); + + +/* Finds the beginning of each key/value pair and stores a pointer in qs_kv. + * Also decodes the value portion of the k/v pair *in-place*. In a future + * enhancement it will also have a compile-time option of sorting qs_kv + * alphabetically by key. */ +size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url); + + +/* Used by qs_parse to decode the value portion of a k/v pair */ +int qs_decode(char * qs); + + +/* Looks up the value according to the key on a pre-processed query string + * A future enhancement will be a compile-time option to look up the key + * in a pre-sorted qs_kv array via a binary search. */ +//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); + char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth); + + +/* Non-destructive lookup of value, based on key. User provides the + * destinaton string and length. */ +char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len); + +// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled +#undef _qsSORTING + +// isxdigit _is_ available in , but let's avoid another header instead +#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0) +#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0) +#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1) + +inline int qs_strncmp(const char * s, const char * qs, size_t n) +{ + unsigned char u1, u2, unyb, lnyb; + + while(n-- > 0) + { + u1 = static_cast(*s++); + u2 = static_cast(*qs++); + + if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; } + if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; } + + if ( u1 == '+' ) { u1 = ' '; } + if ( u1 == '%' ) // easier/safer than scanf + { + unyb = static_cast(*s++); + lnyb = static_cast(*s++); + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u1 = '\0'; + } + + if ( u2 == '+' ) { u2 = ' '; } + if ( u2 == '%' ) // easier/safer than scanf + { + unyb = static_cast(*qs++); + lnyb = static_cast(*qs++); + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u2 = '\0'; + } + + if ( u1 != u2 ) + return u1 - u2; + if ( u1 == '\0' ) + return 0; + } + if ( CROW_QS_ISQSCHR(*qs) ) + return -1; + else + return 0; +} + + +inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true) +{ + size_t i, j; + char * substr_ptr; + + for(i=0; i means x iterations of this loop -> means *x+1* k/v pairs + substr_ptr += j + 1; + i++; + } + + // we only decode the values in place, the keys could have '='s in them + // which will hose our ability to distinguish keys from values later + for(j=0; j> qs_dict_name2kv(const char * dict_name, char * const * qs_kv, size_t qs_kv_size, int nth = 0) +{ + size_t i; + size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close; + + name_len = strlen(dict_name); + +#ifdef _qsSORTING +// TODO: binary search for key in the sorted qs_kv +#else // _qsSORTING + for(i=0; i 0 && + skip_to_brace_close > 0 && + nth == 0 ) + { + auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open); + auto value = std::string(qs_kv[i] + skip_to_eq); + return std::unique_ptr>(new std::pair(key, value)); + } + else + { + --nth; + } + } + } +#endif // _qsSORTING + + return nullptr; +} + + +inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len) +{ + size_t i, key_len; + const char * tmp; + + // find the beginning of the k/v substrings + if ( (tmp = strchr(qs, '?')) != NULL ) + qs = tmp + 1; + + key_len = strlen(key); + while(qs[0] != '#' && qs[0] != '\0') + { + if ( qs_strncmp(key, qs, key_len) == 0 ) + break; + qs += strcspn(qs, "&") + 1; + } + + if ( qs[0] == '\0' ) return NULL; + + qs += strcspn(qs, "=&#"); + if ( qs[0] == '=' ) + { + qs++; + i = strcspn(qs, "&=#"); +#ifdef _MSC_VER + strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); +#else + strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); +#endif + qs_decode(val); + } + else + { + if ( val_len > 0 ) + val[0] = '\0'; + } + + return val; +} +} +// ---------------------------------------------------------------------------- + + +namespace crow +{ + struct request; + /// A class to represent any data coming after the `?` in the request URL into key-value pairs. + class query_string + { + public: + static const int MAX_KEY_VALUE_PAIRS_COUNT = 256; + + query_string() = default; + + query_string(const query_string& qs): + url_(qs.url_) + { + for (auto p : qs.key_value_pairs_) + { + key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); + } + } + + query_string& operator=(const query_string& qs) + { + url_ = qs.url_; + key_value_pairs_.clear(); + for (auto p : qs.key_value_pairs_) + { + key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); + } + return *this; + } + + query_string& operator=(query_string&& qs) noexcept + { + key_value_pairs_ = std::move(qs.key_value_pairs_); + char* old_data = (char*)qs.url_.c_str(); + url_ = std::move(qs.url_); + for (auto& p : key_value_pairs_) + { + p += (char*)url_.c_str() - old_data; + } + return *this; + } + + + query_string(std::string params, bool url = true): + url_(std::move(params)) + { + if (url_.empty()) + return; + + key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); + size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); + + key_value_pairs_.resize(count); + key_value_pairs_.shrink_to_fit(); + } + + void clear() + { + key_value_pairs_.clear(); + url_.clear(); + } + + friend std::ostream& operator<<(std::ostream& os, const query_string& qs) + { + os << "[ "; + for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) + { + if (i) + os << ", "; + os << qs.key_value_pairs_[i]; + } + os << " ]"; + return os; + } + + /// Get a value from a name, used for `?name=value`. + + /// + /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list(). + char* get(const std::string& name) const + { + char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); + return ret; + } + + /// Works similar to \ref get() except it removes the item from the query string. + char* pop(const std::string& name) + { + char* ret = get(name); + if (ret != nullptr) + { + for (unsigned int i = 0; i < key_value_pairs_.size(); i++) + { + std::string str_item(key_value_pairs_[i]); + if (str_item.substr(0, name.size() + 1) == name + '=') + { + key_value_pairs_.erase(key_value_pairs_.begin() + i); + break; + } + } + } + return ret; + } + + /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list. + + /// + /// Note: Square brackets in the above example are controlled by `use_brackets` boolean (true by default). If set to false, the example becomes `?name=value1,name=value2...name=valuen` + std::vector get_list(const std::string& name, bool use_brackets = true) const + { + std::vector ret; + std::string plus = name + (use_brackets ? "[]" : ""); + char* element = nullptr; + + int count = 0; + while (1) + { + element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); + if (!element) + break; + ret.push_back(element); + } + return ret; + } + + /// Similar to \ref get_list() but it removes the + std::vector pop_list(const std::string& name, bool use_brackets = true) + { + std::vector ret = get_list(name, use_brackets); + if (!ret.empty()) + { + for (unsigned int i = 0; i < key_value_pairs_.size(); i++) + { + std::string str_item(key_value_pairs_[i]); + if ((use_brackets ? (str_item.substr(0, name.size() + 3) == name + "[]=") : (str_item.substr(0, name.size() + 1) == name + '='))) + { + key_value_pairs_.erase(key_value_pairs_.begin() + i--); + } + } + } + return ret; + } + + /// Works similar to \ref get_list() except the brackets are mandatory must not be empty. + + /// + /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`. + /// + /// if your query string has both empty brackets and ones with a key inside, use pop_list() to get all the values without a key before running this method. + std::unordered_map get_dict(const std::string& name) const + { + std::unordered_map ret; + + int count = 0; + while (1) + { + if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++)) + ret.insert(*element); + else + break; + } + return ret; + } + + /// Works the same as \ref get_dict() but removes the values from the query string. + std::unordered_map pop_dict(const std::string& name) + { + std::unordered_map ret = get_dict(name); + if (!ret.empty()) + { + for (unsigned int i = 0; i < key_value_pairs_.size(); i++) + { + std::string str_item(key_value_pairs_[i]); + if (str_item.substr(0, name.size() + 1) == name + '[') + { + key_value_pairs_.erase(key_value_pairs_.begin() + i--); + } + } + } + return ret; + } + + std::vector keys() const + { + std::vector keys; + keys.reserve(key_value_pairs_.size()); + + for (const char* const element : key_value_pairs_) + { + const char* delimiter = strchr(element, '='); + if (delimiter) + keys.emplace_back(element, delimiter); + else + keys.emplace_back(element); + } + + return keys; + } + + private: + std::string url_; + std::vector key_value_pairs_; + }; + +} // namespace crow + +#ifdef CROW_ENABLE_COMPRESSION + +#include +#include + +// http://zlib.net/manual.html +namespace crow // NOTE: Already documented in "crow/app.h" +{ + namespace compression + { + // Values used in the 'windowBits' parameter for deflateInit2. + enum algorithm + { + // 15 is the default value for deflate + DEFLATE = 15, + // windowBits can also be greater than 15 for optional gzip encoding. + // Add 16 to windowBits to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper. + GZIP = 15 | 16, + }; + + inline std::string compress_string(std::string const& str, algorithm algo) + { + std::string compressed_str; + z_stream stream{}; + // Initialize with the default values + if (::deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, algo, 8, Z_DEFAULT_STRATEGY) == Z_OK) + { + char buffer[8192]; + + stream.avail_in = str.size(); + // zlib does not take a const pointer. The data is not altered. + stream.next_in = const_cast(reinterpret_cast(str.c_str())); + + int code = Z_OK; + do + { + stream.avail_out = sizeof(buffer); + stream.next_out = reinterpret_cast(&buffer[0]); + + code = ::deflate(&stream, Z_FINISH); + // Successful and non-fatal error code returned by deflate when used with Z_FINISH flush + if (code == Z_OK || code == Z_STREAM_END) + { + std::copy(&buffer[0], &buffer[sizeof(buffer) - stream.avail_out], std::back_inserter(compressed_str)); + } + + } while (code == Z_OK); + + if (code != Z_STREAM_END) + compressed_str.clear(); + + ::deflateEnd(&stream); + } + + return compressed_str; + } + + inline std::string decompress_string(std::string const& deflated_string) + { + std::string inflated_string; + Bytef tmp[8192]; + + z_stream zstream{}; + zstream.avail_in = deflated_string.size(); + // Nasty const_cast but zlib won't alter its contents + zstream.next_in = const_cast(reinterpret_cast(deflated_string.c_str())); + // Initialize with automatic header detection, for gzip support + if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK) + { + do + { + zstream.avail_out = sizeof(tmp); + zstream.next_out = &tmp[0]; + + auto ret = ::inflate(&zstream, Z_NO_FLUSH); + if (ret == Z_OK || ret == Z_STREAM_END) + { + std::copy(&tmp[0], &tmp[sizeof(tmp) - zstream.avail_out], std::back_inserter(inflated_string)); + } + else + { + // Something went wrong with inflate; make sure we return an empty string + inflated_string.clear(); + break; + } + + } while (zstream.avail_out == 0); + + // Free zlib's internal memory + ::inflateEnd(&zstream); + } + + return inflated_string; + } + } // namespace compression +} // namespace crow + +#endif + +/* + * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 + * + * Copyright (c) 2012-22 SAURAV MOHAPATRA + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * \file TinySHA1.hpp + * \author SAURAV MOHAPATRA + * \date 2012-22 + * \brief TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based + * on the implementation in boost::uuid::details. + * + * In this file are defined: + * - sha1::SHA1 + */ +#ifndef _TINY_SHA1_HPP_ +#define _TINY_SHA1_HPP_ +#include +#include +#include +#include + +/** + * \namespace sha1 + * \brief Here is defined the SHA1 class + */ +namespace sha1 +{ + /** + * \class SHA1 + * \brief A tiny SHA1 algorithm implementation used internally in the + * Crow server (specifically in crow/websocket.h). + */ + class SHA1 + { + public: + typedef uint32_t digest32_t[5]; + typedef uint8_t digest8_t[20]; + inline static uint32_t LeftRotate(uint32_t value, size_t count) { + return (value << count) ^ (value >> (32-count)); + } + SHA1(){ reset(); } + virtual ~SHA1() {} + SHA1(const SHA1& s) { *this = s; } + const SHA1& operator = (const SHA1& s) { + memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); + memcpy(m_block, s.m_block, 64); + m_blockByteIndex = s.m_blockByteIndex; + m_byteCount = s.m_byteCount; + return *this; + } + SHA1& reset() { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + return *this; + } + SHA1& processByte(uint8_t octet) { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if(m_blockByteIndex == 64) { + this->m_blockByteIndex = 0; + processBlock(); + } + return *this; + } + SHA1& processBlock(const void* const start, const void* const end) { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while(begin != finish) { + processByte(*begin); + begin++; + } + return *this; + } + SHA1& processBytes(const void* const data, size_t len) { + const uint8_t* block = static_cast(data); + processBlock(block, block + len); + return *this; + } + const uint32_t* getDigest(digest32_t digest) { + size_t bitCount = this->m_byteCount * 8; + processByte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + processByte(0); + } + while (m_blockByteIndex < 56) { + processByte(0); + } + } else { + while (m_blockByteIndex < 56) { + processByte(0); + } + } + processByte(0); + processByte(0); + processByte(0); + processByte(0); + processByte( static_cast((bitCount>>24) & 0xFF)); + processByte( static_cast((bitCount>>16) & 0xFF)); + processByte( static_cast((bitCount>>8 ) & 0xFF)); + processByte( static_cast((bitCount) & 0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + const uint8_t* getDigestBytes(digest8_t digest) { + digest32_t d32; + getDigest(d32); + size_t di = 0; + digest[di++] = ((d32[0] >> 24) & 0xFF); + digest[di++] = ((d32[0] >> 16) & 0xFF); + digest[di++] = ((d32[0] >> 8) & 0xFF); + digest[di++] = ((d32[0]) & 0xFF); + + digest[di++] = ((d32[1] >> 24) & 0xFF); + digest[di++] = ((d32[1] >> 16) & 0xFF); + digest[di++] = ((d32[1] >> 8) & 0xFF); + digest[di++] = ((d32[1]) & 0xFF); + + digest[di++] = ((d32[2] >> 24) & 0xFF); + digest[di++] = ((d32[2] >> 16) & 0xFF); + digest[di++] = ((d32[2] >> 8) & 0xFF); + digest[di++] = ((d32[2]) & 0xFF); + + digest[di++] = ((d32[3] >> 24) & 0xFF); + digest[di++] = ((d32[3] >> 16) & 0xFF); + digest[di++] = ((d32[3] >> 8) & 0xFF); + digest[di++] = ((d32[3]) & 0xFF); + + digest[di++] = ((d32[4] >> 24) & 0xFF); + digest[di++] = ((d32[4] >> 16) & 0xFF); + digest[di++] = ((d32[4] >> 8) & 0xFF); + digest[di++] = ((d32[4]) & 0xFF); + return digest; + } + + protected: + void processBlock() { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = (m_block[i*4 + 0] << 24); + w[i] |= (m_block[i*4 + 1] << 16); + w[i] |= (m_block[i*4 + 2] << 8); + w[i] |= (m_block[i*4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = LeftRotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i=0; i<80; ++i) { + uint32_t f = 0; + uint32_t k = 0; + + if (i<20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i<40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i<60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = LeftRotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; + }; +} +#endif + +// settings for crow +// TODO(ipkn) replace with runtime config. libucl? + +/* #ifdef - enables debug mode */ +//#define CROW_ENABLE_DEBUG + +/* #ifdef - enables logging */ +#define CROW_ENABLE_LOGGING + +/* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked messages from clients) */ +//#define CROW_ENFORCE_WS_SPEC + +/* #define - specifies log level */ +/* + Debug = 0 + Info = 1 + Warning = 2 + Error = 3 + Critical = 4 + + default to INFO +*/ +#ifndef CROW_LOG_LEVEL +#define CROW_LOG_LEVEL 1 +#endif + +#ifndef CROW_STATIC_DIRECTORY +#define CROW_STATIC_DIRECTORY "static/" +#endif +#ifndef CROW_STATIC_ENDPOINT +#define CROW_STATIC_ENDPOINT "/static/" +#endif + +// compiler flags +#if defined(_MSVC_LANG) && _MSVC_LANG >= 201402L +#define CROW_CAN_USE_CPP14 +#endif +#if __cplusplus >= 201402L +#define CROW_CAN_USE_CPP14 +#endif + +#if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L +#define CROW_CAN_USE_CPP17 +#endif +#if __cplusplus >= 201703L +#define CROW_CAN_USE_CPP17 +#if defined(__GNUC__) && __GNUC__ < 8 +#define CROW_FILESYSTEM_IS_EXPERIMENTAL +#endif +#endif + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#define CROW_MSVC_WORKAROUND +#define constexpr const +#define noexcept throw() +#endif +#endif + +#if defined(__GNUC__) && __GNUC__ == 8 && __GNUC_MINOR__ < 4 +#if __cplusplus > 201103L +#define CROW_GCC83_WORKAROUND +#else +#error "GCC 8.1 - 8.3 has a bug that prevents Crow from compiling with C++11. Please update GCC to > 8.3 or use C++ > 11." +#endif +#endif + + +#ifdef CROW_USE_BOOST +#include +#include +#ifdef CROW_ENABLE_SSL +#include +#endif +#else +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif +#include +#include +#ifdef CROW_ENABLE_SSL +#include +#endif +#endif + +#if (CROW_USE_BOOST && BOOST_VERSION >= 107000) || (ASIO_VERSION >= 101300) +#define GET_IO_SERVICE(s) ((asio::io_context&)(s).get_executor().context()) +#else +#define GET_IO_SERVICE(s) ((s).get_io_service()) +#endif + +namespace crow +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; + using error_code = boost::system::error_code; +#else + using error_code = asio::error_code; +#endif + using tcp = asio::ip::tcp; + + /// A wrapper for the asio::ip::tcp::socket and asio::ssl::stream + struct SocketAdaptor + { + using context = void; + SocketAdaptor(asio::io_service& io_service, context*): + socket_(io_service) + {} + + asio::io_service& get_io_service() + { + return GET_IO_SERVICE(socket_); + } + + /// Get the TCP socket handling data trasfers, regardless of what layer is handling transfers on top of the socket. + tcp::socket& raw_socket() + { + return socket_; + } + + /// Get the object handling data transfers, this can be either a TCP socket or an SSL stream (if SSL is enabled). + tcp::socket& socket() + { + return socket_; + } + + tcp::endpoint remote_endpoint() + { + return socket_.remote_endpoint(); + } + + bool is_open() + { + return socket_.is_open(); + } + + void close() + { + error_code ec; + socket_.close(ec); + } + + void shutdown_readwrite() + { + error_code ec; + socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); + } + + void shutdown_write() + { + error_code ec; + socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); + } + + void shutdown_read() + { + error_code ec; + socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); + } + + template + void start(F f) + { + f(error_code()); + } + + tcp::socket socket_; + }; + +#ifdef CROW_ENABLE_SSL + struct SSLAdaptor + { + using context = asio::ssl::context; + using ssl_socket_t = asio::ssl::stream; + SSLAdaptor(asio::io_service& io_service, context* ctx): + ssl_socket_(new ssl_socket_t(io_service, *ctx)) + {} + + asio::ssl::stream& socket() + { + return *ssl_socket_; + } + + tcp::socket::lowest_layer_type& + raw_socket() + { + return ssl_socket_->lowest_layer(); + } + + tcp::endpoint remote_endpoint() + { + return raw_socket().remote_endpoint(); + } + + bool is_open() + { + return ssl_socket_ ? raw_socket().is_open() : false; + } + + void close() + { + if (is_open()) + { + error_code ec; + raw_socket().close(ec); + } + } + + void shutdown_readwrite() + { + if (is_open()) + { + error_code ec; + raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); + } + } + + void shutdown_write() + { + if (is_open()) + { + error_code ec; + raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); + } + } + + void shutdown_read() + { + if (is_open()) + { + error_code ec; + raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); + } + } + + asio::io_service& get_io_service() + { + return GET_IO_SERVICE(raw_socket()); + } + + template + void start(F f) + { + ssl_socket_->async_handshake(asio::ssl::stream_base::server, + [f](const error_code& ec) { + f(ec); + }); + } + + std::unique_ptr> ssl_socket_; + }; +#endif +} // namespace crow + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined(CROW_CAN_USE_CPP17) && !defined(CROW_FILESYSTEM_IS_EXPERIMENTAL) +#include +#endif + +// TODO(EDev): Adding C++20's [[likely]] and [[unlikely]] attributes might be useful +#if defined(__GNUG__) || defined(__clang__) +#define CROW_LIKELY(X) __builtin_expect(!!(X), 1) +#define CROW_UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +#define CROW_LIKELY(X) (X) +#define CROW_UNLIKELY(X) (X) +#endif + +namespace crow +{ + /// @cond SKIP + namespace black_magic + { +#ifndef CROW_MSVC_WORKAROUND + /// Out of Range Exception for const_str + struct OutOfRange + { + OutOfRange(unsigned /*pos*/, unsigned /*length*/) {} + }; + /// Helper function to throw an exception if i is larger than len + constexpr unsigned requires_in_range(unsigned i, unsigned len) + { + return i >= len ? throw OutOfRange(i, len) : i; + } + + /// A constant string implementation. + class const_str + { + const char* const begin_; + unsigned size_; + + public: + template + constexpr const_str(const char (&arr)[N]): + begin_(arr), size_(N - 1) + { + static_assert(N >= 1, "not a string literal"); + } + constexpr char operator[](unsigned i) const + { + return requires_in_range(i, size_), begin_[i]; + } + + constexpr operator const char*() const + { + return begin_; + } + + constexpr const char* begin() const { return begin_; } + constexpr const char* end() const { return begin_ + size_; } + + constexpr unsigned size() const + { + return size_; + } + }; + + constexpr unsigned find_closing_tag(const_str s, unsigned p) + { + return s[p] == '>' ? p : find_closing_tag(s, p + 1); + } + + /// Check that the CROW_ROUTE string is valid + constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0) + { + return i == s.size() ? f == 0 : + f < 0 || f >= 2 ? false : + s[i] == '<' ? is_valid(s, i + 1, f + 1) : + s[i] == '>' ? is_valid(s, i + 1, f - 1) : + is_valid(s, i + 1, f); + } + + constexpr bool is_equ_p(const char* a, const char* b, unsigned n) + { + return *a == 0 && *b == 0 && n == 0 ? true : + (*a == 0 || *b == 0) ? false : + n == 0 ? true : + *a != *b ? false : + is_equ_p(a + 1, b + 1, n - 1); + } + + constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n) + { + return ai + n > a.size() || bi + n > b.size() ? false : + n == 0 ? true : + a[ai] != b[bi] ? false : + is_equ_n(a, ai + 1, b, bi + 1, n - 1); + } + + constexpr bool is_int(const_str s, unsigned i) + { + return is_equ_n(s, i, "", 0, 5); + } + + constexpr bool is_uint(const_str s, unsigned i) + { + return is_equ_n(s, i, "", 0, 6); + } + + constexpr bool is_float(const_str s, unsigned i) + { + return is_equ_n(s, i, "", 0, 7) || + is_equ_n(s, i, "", 0, 8); + } + + constexpr bool is_str(const_str s, unsigned i) + { + return is_equ_n(s, i, "", 0, 5) || + is_equ_n(s, i, "", 0, 8); + } + + constexpr bool is_path(const_str s, unsigned i) + { + return is_equ_n(s, i, "", 0, 6); + } +#endif + template + struct parameter_tag + { + static const int value = 0; + }; +#define CROW_INTERNAL_PARAMETER_TAG(t, i) \ + template<> \ + struct parameter_tag \ + { \ + static const int value = i; \ + } + CROW_INTERNAL_PARAMETER_TAG(int, 1); + CROW_INTERNAL_PARAMETER_TAG(char, 1); + CROW_INTERNAL_PARAMETER_TAG(short, 1); + CROW_INTERNAL_PARAMETER_TAG(long, 1); + CROW_INTERNAL_PARAMETER_TAG(long long, 1); + CROW_INTERNAL_PARAMETER_TAG(unsigned int, 2); + CROW_INTERNAL_PARAMETER_TAG(unsigned char, 2); + CROW_INTERNAL_PARAMETER_TAG(unsigned short, 2); + CROW_INTERNAL_PARAMETER_TAG(unsigned long, 2); + CROW_INTERNAL_PARAMETER_TAG(unsigned long long, 2); + CROW_INTERNAL_PARAMETER_TAG(double, 3); + CROW_INTERNAL_PARAMETER_TAG(std::string, 4); +#undef CROW_INTERNAL_PARAMETER_TAG + template + struct compute_parameter_tag_from_args_list; + + template<> + struct compute_parameter_tag_from_args_list<> + { + static const int value = 0; + }; + + template + struct compute_parameter_tag_from_args_list + { + static const int sub_value = + compute_parameter_tag_from_args_list::value; + static const int value = + parameter_tag::type>::value ? sub_value * 6 + parameter_tag::type>::value : sub_value; + }; + + static inline bool is_parameter_tag_compatible(uint64_t a, uint64_t b) + { + if (a == 0) + return b == 0; + if (b == 0) + return a == 0; + int sa = a % 6; + int sb = a % 6; + if (sa == 5) sa = 4; + if (sb == 5) sb = 4; + if (sa != sb) + return false; + return is_parameter_tag_compatible(a / 6, b / 6); + } + + static inline unsigned find_closing_tag_runtime(const char* s, unsigned p) + { + return s[p] == 0 ? throw std::runtime_error("unmatched tag <") : + s[p] == '>' ? p : + find_closing_tag_runtime(s, p + 1); + } + + static inline uint64_t get_parameter_tag_runtime(const char* s, unsigned p = 0) + { + return s[p] == 0 ? 0 : + s[p] == '<' ? ( + std::strncmp(s + p, "", 5) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 1 : + std::strncmp(s + p, "", 6) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 2 : + (std::strncmp(s + p, "", 7) == 0 || + std::strncmp(s + p, "", 8) == 0) ? + get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 3 : + (std::strncmp(s + p, "", 5) == 0 || + std::strncmp(s + p, "", 8) == 0) ? + get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 4 : + std::strncmp(s + p, "", 6) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 5 : + throw std::runtime_error("invalid parameter type")) : + get_parameter_tag_runtime(s, p + 1); + } +#ifndef CROW_MSVC_WORKAROUND + constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0) + { + return p == s.size() ? 0 : + s[p] == '<' ? ( + is_int(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 : + is_uint(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 : + is_float(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 : + is_str(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 : + is_path(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 : + throw std::runtime_error("invalid parameter type")) : + get_parameter_tag(s, p + 1); + } +#endif + + template + struct S + { + template + using push = S; + template + using push_back = S; + template class U> + using rebind = U; + }; + + // Check whether the template function can be called with specific arguments + template + struct CallHelper; + template + struct CallHelper> + { + template()(std::declval()...))> + static char __test(int); + + template + static int __test(...); + + static constexpr bool value = sizeof(__test(0)) == sizeof(char); + }; + + // Check Tuple contains type T + template + struct has_type; + + template + struct has_type> : std::false_type + {}; + + template + struct has_type> : has_type> + {}; + + template + struct has_type> : std::true_type + {}; + + // Find index of type in tuple + template + struct tuple_index; + + template + struct tuple_index> + { + static const int value = 0; + }; + + template + struct tuple_index> + { + static const int value = 1 + tuple_index>::value; + }; + + // Extract element from forward tuple or get default +#ifdef CROW_CAN_USE_CPP14 + template + typename std::enable_if::value, typename std::decay::type&&>::type + tuple_extract(Tup& tup) + { + return std::move(std::get(tup)); + } +#else + template + typename std::enable_if::value, T&&>::type + tuple_extract(Tup& tup) + { + return std::move(std::get::value>(tup)); + } +#endif + + template + typename std::enable_if::value, T>::type + tuple_extract(Tup&) + { + return T{}; + } + + // Kind of fold expressions in C++11 + template + struct bool_pack; + template + using all_true = std::is_same, bool_pack>; + + template + struct single_tag_to_type + {}; + + template<> + struct single_tag_to_type<1> + { + using type = int64_t; + }; + + template<> + struct single_tag_to_type<2> + { + using type = uint64_t; + }; + + template<> + struct single_tag_to_type<3> + { + using type = double; + }; + + template<> + struct single_tag_to_type<4> + { + using type = std::string; + }; + + template<> + struct single_tag_to_type<5> + { + using type = std::string; + }; + + + template + struct arguments + { + using subarguments = typename arguments::type; + using type = + typename subarguments::template push::type>; + }; + + template<> + struct arguments<0> + { + using type = S<>; + }; + + template + struct last_element_type + { + using type = typename std::tuple_element>::type; + }; + + + template<> + struct last_element_type<> + {}; + + + // from http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth + template + using Invoke = typename T::type; + + template + struct seq + { + using type = seq; + }; + + template + struct concat; + + template + struct concat, seq> : seq + {}; + + template + using Concat = Invoke>; + + template + struct gen_seq; + template + using GenSeq = Invoke>; + + template + struct gen_seq : Concat, GenSeq> + {}; + + template<> + struct gen_seq<0> : seq<> + {}; + template<> + struct gen_seq<1> : seq<0> + {}; + + template + struct pop_back_helper; + + template + struct pop_back_helper, Tuple> + { + template class U> + using rebind = U::type...>; + }; + + template + struct pop_back //: public pop_back_helper::type, std::tuple> + { + template class U> + using rebind = typename pop_back_helper::type, std::tuple>::template rebind; + }; + + template<> + struct pop_back<> + { + template class U> + using rebind = U<>; + }; + + // from http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type + template + struct contains : std::true_type + {}; + + template + struct contains : std::conditional::value, std::true_type, contains>::type + {}; + + template + struct contains : std::false_type + {}; + + template + struct empty_context + {}; + + template + struct promote + { + using type = T; + }; + +#define CROW_INTERNAL_PROMOTE_TYPE(t1, t2) \ + template<> \ + struct promote \ + { \ + using type = t2; \ + } + + CROW_INTERNAL_PROMOTE_TYPE(char, int64_t); + CROW_INTERNAL_PROMOTE_TYPE(short, int64_t); + CROW_INTERNAL_PROMOTE_TYPE(int, int64_t); + CROW_INTERNAL_PROMOTE_TYPE(long, int64_t); + CROW_INTERNAL_PROMOTE_TYPE(long long, int64_t); + CROW_INTERNAL_PROMOTE_TYPE(unsigned char, uint64_t); + CROW_INTERNAL_PROMOTE_TYPE(unsigned short, uint64_t); + CROW_INTERNAL_PROMOTE_TYPE(unsigned int, uint64_t); + CROW_INTERNAL_PROMOTE_TYPE(unsigned long, uint64_t); + CROW_INTERNAL_PROMOTE_TYPE(unsigned long long, uint64_t); + CROW_INTERNAL_PROMOTE_TYPE(float, double); +#undef CROW_INTERNAL_PROMOTE_TYPE + + template + using promote_t = typename promote::type; + + } // namespace black_magic + + namespace detail + { + + template + struct get_index_of_element_from_tuple_by_type_impl + { + static constexpr auto value = N; + }; + + template + struct get_index_of_element_from_tuple_by_type_impl + { + static constexpr auto value = N; + }; + + template + struct get_index_of_element_from_tuple_by_type_impl + { + static constexpr auto value = get_index_of_element_from_tuple_by_type_impl::value; + }; + } // namespace detail + + namespace utility + { + template + T& get_element_by_type(std::tuple& t) + { + return std::get::value>(t); + } + + template + struct function_traits; + +#ifndef CROW_MSVC_WORKAROUND + template + struct function_traits : public function_traits + { + using parent_t = function_traits; + static const size_t arity = parent_t::arity; + using result_type = typename parent_t::result_type; + template + using arg = typename parent_t::template arg; + }; +#endif + + template + struct function_traits + { + static const size_t arity = sizeof...(Args); + + typedef R result_type; + + template + using arg = typename std::tuple_element>::type; + }; + + template + struct function_traits + { + static const size_t arity = sizeof...(Args); + + typedef R result_type; + + template + using arg = typename std::tuple_element>::type; + }; + + template + struct function_traits> + { + static const size_t arity = sizeof...(Args); + + typedef R result_type; + + template + using arg = typename std::tuple_element>::type; + }; + /// @endcond + + inline static std::string base64encode(const unsigned char* data, size_t size, const char* key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + { + std::string ret; + ret.resize((size + 2) / 3 * 4); + auto it = ret.begin(); + while (size >= 3) + { + *it++ = key[(static_cast(*data) & 0xFC) >> 2]; + unsigned char h = (static_cast(*data++) & 0x03) << 4; + *it++ = key[h | ((static_cast(*data) & 0xF0) >> 4)]; + h = (static_cast(*data++) & 0x0F) << 2; + *it++ = key[h | ((static_cast(*data) & 0xC0) >> 6)]; + *it++ = key[static_cast(*data++) & 0x3F]; + + size -= 3; + } + if (size == 1) + { + *it++ = key[(static_cast(*data) & 0xFC) >> 2]; + unsigned char h = (static_cast(*data++) & 0x03) << 4; + *it++ = key[h]; + *it++ = '='; + *it++ = '='; + } + else if (size == 2) + { + *it++ = key[(static_cast(*data) & 0xFC) >> 2]; + unsigned char h = (static_cast(*data++) & 0x03) << 4; + *it++ = key[h | ((static_cast(*data) & 0xF0) >> 4)]; + h = (static_cast(*data++) & 0x0F) << 2; + *it++ = key[h]; + *it++ = '='; + } + return ret; + } + + inline static std::string base64encode(std::string data, size_t size, const char* key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + { + return base64encode((const unsigned char*)data.c_str(), size, key); + } + + inline static std::string base64encode_urlsafe(const unsigned char* data, size_t size) + { + return base64encode(data, size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); + } + + inline static std::string base64encode_urlsafe(std::string data, size_t size) + { + return base64encode((const unsigned char*)data.c_str(), size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); + } + + inline static std::string base64decode(const char* data, size_t size) + { + // We accept both regular and url encoding here, as there does not seem to be any downside to that. + // If we want to distinguish that we should use +/ for non-url and -_ for url. + + // Mapping logic from characters to [0-63] + auto key = [](char c) -> unsigned char { + if ((c >= 'A') && (c <= 'Z')) return c - 'A'; + if ((c >= 'a') && (c <= 'z')) return c - 'a' + 26; + if ((c >= '0') && (c <= '9')) return c - '0' + 52; + if ((c == '+') || (c == '-')) return 62; + if ((c == '/') || (c == '_')) return 63; + return 0; + }; + + // Not padded + if (size % 4 == 2) // missing last 2 characters + size = (size / 4 * 3) + 1; // Not subtracting extra characters because they're truncated in int division + else if (size % 4 == 3) // missing last character + size = (size / 4 * 3) + 2; // Not subtracting extra characters because they're truncated in int division + + // Padded + else if (data[size - 2] == '=') // padded with '==' + size = (size / 4 * 3) - 2; // == padding means the last block only has 1 character instead of 3, hence the '-2' + else if (data[size - 1] == '=') // padded with '=' + size = (size / 4 * 3) - 1; // = padding means the last block only has 2 character instead of 3, hence the '-1' + + // Padding not needed + else + size = size / 4 * 3; + + std::string ret; + ret.resize(size); + auto it = ret.begin(); + + // These will be used to decode 1 character at a time + unsigned char odd; // char1 and char3 + unsigned char even; // char2 and char4 + + // Take 4 character blocks to turn into 3 + while (size >= 3) + { + // dec_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right)) + odd = key(*data++); + even = key(*data++); + *it++ = (odd << 2) | ((even & 0x30) >> 4); + // dec_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right)) + odd = key(*data++); + *it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2); + // dec_char3 = ((char3 AND 00000011) shifted 6 bits left) OR (char4) + even = key(*data++); + *it++ = ((odd & 0x03) << 6) | (even); + + size -= 3; + } + if (size == 2) + { + // d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right)) + odd = key(*data++); + even = key(*data++); + *it++ = (odd << 2) | ((even & 0x30) >> 4); + // d_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right)) + odd = key(*data++); + *it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2); + } + else if (size == 1) + { + // d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right)) + odd = key(*data++); + even = key(*data++); + *it++ = (odd << 2) | ((even & 0x30) >> 4); + } + return ret; + } + + inline static std::string base64decode(const std::string& data, size_t size) + { + return base64decode(data.data(), size); + } + + inline static std::string base64decode(const std::string& data) + { + return base64decode(data.data(), data.length()); + } + + inline static std::string normalize_path(const std::string& directoryPath) + { + std::string normalizedPath = directoryPath; + std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/'); + if (!normalizedPath.empty() && normalizedPath.back() != '/') + normalizedPath += '/'; + return normalizedPath; + } + + inline static void sanitize_filename(std::string& data, char replacement = '_') + { + if (data.length() > 255) + data.resize(255); + + static const auto toUpper = [](char c) { + return ((c >= 'a') && (c <= 'z')) ? (c - ('a' - 'A')) : c; + }; + // Check for special device names. The Windows behavior is really odd here, it will consider both AUX and AUX.txt + // a special device. Thus we search for the string (case-insensitive), and then check if the string ends or if + // is has a dangerous follow up character (.:\/) + auto sanitizeSpecialFile = [](std::string& source, unsigned ofs, const char* pattern, bool includeNumber, char replacement) { + unsigned i = ofs; + size_t len = source.length(); + const char* p = pattern; + while (*p) + { + if (i >= len) return; + if (toUpper(source[i]) != *p) return; + ++i; + ++p; + } + if (includeNumber) + { + if ((i >= len) || (source[i] < '1') || (source[i] > '9')) return; + ++i; + } + if ((i >= len) || (source[i] == '.') || (source[i] == ':') || (source[i] == '/') || (source[i] == '\\')) + { + source.erase(ofs + 1, (i - ofs) - 1); + source[ofs] = replacement; + } + }; + bool checkForSpecialEntries = true; + for (unsigned i = 0; i < data.length(); ++i) + { + // Recognize directory traversals and the special devices CON/PRN/AUX/NULL/COM[1-]/LPT[1-9] + if (checkForSpecialEntries) + { + checkForSpecialEntries = false; + switch (toUpper(data[i])) + { + case 'A': + sanitizeSpecialFile(data, i, "AUX", false, replacement); + break; + case 'C': + sanitizeSpecialFile(data, i, "CON", false, replacement); + sanitizeSpecialFile(data, i, "COM", true, replacement); + break; + case 'L': + sanitizeSpecialFile(data, i, "LPT", true, replacement); + break; + case 'N': + sanitizeSpecialFile(data, i, "NUL", false, replacement); + break; + case 'P': + sanitizeSpecialFile(data, i, "PRN", false, replacement); + break; + case '.': + sanitizeSpecialFile(data, i, "..", false, replacement); + break; + } + } + + // Sanitize individual characters + unsigned char c = data[i]; + if ((c < ' ') || ((c >= 0x80) && (c <= 0x9F)) || (c == '?') || (c == '<') || (c == '>') || (c == ':') || (c == '*') || (c == '|') || (c == '\"')) + { + data[i] = replacement; + } + else if ((c == '/') || (c == '\\')) + { + if (CROW_UNLIKELY(i == 0)) //Prevent Unix Absolute Paths (Windows Absolute Paths are prevented with `(c == ':')`) + { + data[i] = replacement; + } + else + { + checkForSpecialEntries = true; + } + } + } + } + + inline static std::string random_alphanum(std::size_t size) + { + static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(0, sizeof(alphabet) - 2); + std::string out; + out.reserve(size); + for (std::size_t i = 0; i < size; i++) + out.push_back(alphabet[dist(rng)]); + return out; + } + + inline static std::string join_path(std::string path, const std::string& fname) + { +#if defined(CROW_CAN_USE_CPP17) && !defined(CROW_FILESYSTEM_IS_EXPERIMENTAL) + return (std::filesystem::path(path) / fname).string(); +#else + if (!(path.back() == '/' || path.back() == '\\')) + path += '/'; + path += fname; + return path; +#endif + } + + /** + * @brief Checks two string for equality. + * Always returns false if strings differ in size. + * Defaults to case-insensitive comparison. + */ + inline static bool string_equals(const std::string& l, const std::string& r, bool case_sensitive = false) + { + if (l.length() != r.length()) + return false; + + for (size_t i = 0; i < l.length(); i++) + { + if (case_sensitive) + { + if (l[i] != r[i]) + return false; + } + else + { + if (std::toupper(l[i]) != std::toupper(r[i])) + return false; + } + } + + return true; + } + + template + inline static T lexical_cast(const U& v) + { + std::stringstream stream; + T res; + + stream << v; + stream >> res; + + return res; + } + + template + inline static T lexical_cast(const char* v, size_t count) + { + std::stringstream stream; + T res; + + stream.write(v, count); + stream >> res; + + return res; + } + + + /// Return a copy of the given string with its + /// leading and trailing whitespaces removed. + inline static std::string trim(const std::string& v) + { + if (v.empty()) + return ""; + + size_t begin = 0, end = v.length(); + + size_t i; + for (i = 0; i < v.length(); i++) + { + if (!std::isspace(v[i])) + { + begin = i; + break; + } + } + + if (i == v.length()) + return ""; + + for (i = v.length(); i > 0; i--) + { + if (!std::isspace(v[i - 1])) + { + end = i; + break; + } + } + + return v.substr(begin, end - begin); + } + } // namespace utility +} // namespace crow + + +#include +#include + +namespace crow +{ + /// Hashing function for ci_map (unordered_multimap). + struct ci_hash + { + size_t operator()(const std::string& key) const + { + std::size_t seed = 0; + std::locale locale; + + for (auto c : key) + hash_combine(seed, std::toupper(c, locale)); + + return seed; + } + + private: + static inline void hash_combine(std::size_t& seed, char v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + }; + + /// Equals function for ci_map (unordered_multimap). + struct ci_key_eq + { + bool operator()(const std::string& l, const std::string& r) const + { + return utility::string_equals(l, r); + } + }; + + using ci_map = std::unordered_multimap; +} // namespace crow + + +#include +#include +#include +#include + +namespace crow +{ + const char cr = '\r'; + const char lf = '\n'; + const std::string crlf("\r\n"); + + enum class HTTPMethod : char + { +#ifndef DELETE + DELETE = 0, + GET, + HEAD, + POST, + PUT, + + CONNECT, + OPTIONS, + TRACE, + + PATCH, + PURGE, + + COPY, + LOCK, + MKCOL, + MOVE, + PROPFIND, + PROPPATCH, + SEARCH, + UNLOCK, + BIND, + REBIND, + UNBIND, + ACL, + + REPORT, + MKACTIVITY, + CHECKOUT, + MERGE, + + MSEARCH, + NOTIFY, + SUBSCRIBE, + UNSUBSCRIBE, + + MKCALENDAR, + + LINK, + UNLINK, + + SOURCE, +#endif + + Delete = 0, + Get, + Head, + Post, + Put, + + Connect, + Options, + Trace, + + Patch, + Purge, + + Copy, + Lock, + MkCol, + Move, + Propfind, + Proppatch, + Search, + Unlock, + Bind, + Rebind, + Unbind, + Acl, + + Report, + MkActivity, + Checkout, + Merge, + + MSearch, + Notify, + Subscribe, + Unsubscribe, + + MkCalendar, + + Link, + Unlink, + + Source, + + + InternalMethodCount, + // should not add an item below this line: used for array count + }; + + constexpr const char* method_strings[] = + { + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + + "CONNECT", + "OPTIONS", + "TRACE", + + "PATCH", + "PURGE", + + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + + "MKCALENDAR", + + "LINK", + "UNLINK", + + "SOURCE"}; + + + inline std::string method_name(HTTPMethod method) + { + if (CROW_LIKELY(method < HTTPMethod::InternalMethodCount)) + { + return method_strings[(unsigned char)method]; + } + return "invalid"; + } + + // clang-format off + + enum status + { + CONTINUE = 100, + SWITCHING_PROTOCOLS = 101, + + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NON_AUTHORITATIVE_INFORMATION = 203, + NO_CONTENT = 204, + RESET_CONTENT = 205, + PARTIAL_CONTENT = 206, + + MULTIPLE_CHOICES = 300, + MOVED_PERMANENTLY = 301, + FOUND = 302, + SEE_OTHER = 303, + NOT_MODIFIED = 304, + TEMPORARY_REDIRECT = 307, + PERMANENT_REDIRECT = 308, + + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NOT_FOUND = 404, + METHOD_NOT_ALLOWED = 405, + NOT_ACCEPTABLE = 406, + PROXY_AUTHENTICATION_REQUIRED = 407, + CONFLICT = 409, + GONE = 410, + PAYLOAD_TOO_LARGE = 413, + UNSUPPORTED_MEDIA_TYPE = 415, + RANGE_NOT_SATISFIABLE = 416, + EXPECTATION_FAILED = 417, + PRECONDITION_REQUIRED = 428, + TOO_MANY_REQUESTS = 429, + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503, + GATEWAY_TIMEOUT = 504, + VARIANT_ALSO_NEGOTIATES = 506 + }; + + // clang-format on + + enum class ParamType : char + { + INT, + UINT, + DOUBLE, + STRING, + PATH, + + MAX + }; + + /// @cond SKIP + struct routing_params + { + std::vector int_params; + std::vector uint_params; + std::vector double_params; + std::vector string_params; + + void debug_print() const + { + std::cerr << "routing_params" << std::endl; + for (auto i : int_params) + std::cerr << i << ", "; + std::cerr << std::endl; + for (auto i : uint_params) + std::cerr << i << ", "; + std::cerr << std::endl; + for (auto i : double_params) + std::cerr << i << ", "; + std::cerr << std::endl; + for (auto& i : string_params) + std::cerr << i << ", "; + std::cerr << std::endl; + } + + template + T get(unsigned) const; + }; + + template<> + inline int64_t routing_params::get(unsigned index) const + { + return int_params[index]; + } + + template<> + inline uint64_t routing_params::get(unsigned index) const + { + return uint_params[index]; + } + + template<> + inline double routing_params::get(unsigned index) const + { + return double_params[index]; + } + + template<> + inline std::string routing_params::get(unsigned index) const + { + return string_params[index]; + } + /// @endcond + + struct routing_handle_result + { + uint16_t rule_index; + std::vector blueprint_indices; + routing_params r_params; + HTTPMethod method; + + routing_handle_result() {} + + routing_handle_result(uint16_t rule_index_, std::vector blueprint_indices_, routing_params r_params_): + rule_index(rule_index_), + blueprint_indices(blueprint_indices_), + r_params(r_params_) {} + + routing_handle_result(uint16_t rule_index_, std::vector blueprint_indices_, routing_params r_params_, HTTPMethod method_): + rule_index(rule_index_), + blueprint_indices(blueprint_indices_), + r_params(r_params_), + method(method_) {} + }; +} // namespace crow + +// clang-format off +#ifndef CROW_MSVC_WORKAROUND +constexpr crow::HTTPMethod method_from_string(const char* str) +{ + return crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::Get : + crow::black_magic::is_equ_p(str, "DELETE", 6) ? crow::HTTPMethod::Delete : + crow::black_magic::is_equ_p(str, "HEAD", 4) ? crow::HTTPMethod::Head : + crow::black_magic::is_equ_p(str, "POST", 4) ? crow::HTTPMethod::Post : + crow::black_magic::is_equ_p(str, "PUT", 3) ? crow::HTTPMethod::Put : + + crow::black_magic::is_equ_p(str, "OPTIONS", 7) ? crow::HTTPMethod::Options : + crow::black_magic::is_equ_p(str, "CONNECT", 7) ? crow::HTTPMethod::Connect : + crow::black_magic::is_equ_p(str, "TRACE", 5) ? crow::HTTPMethod::Trace : + + crow::black_magic::is_equ_p(str, "PATCH", 5) ? crow::HTTPMethod::Patch : + crow::black_magic::is_equ_p(str, "PURGE", 5) ? crow::HTTPMethod::Purge : + crow::black_magic::is_equ_p(str, "COPY", 4) ? crow::HTTPMethod::Copy : + crow::black_magic::is_equ_p(str, "LOCK", 4) ? crow::HTTPMethod::Lock : + crow::black_magic::is_equ_p(str, "MKCOL", 5) ? crow::HTTPMethod::MkCol : + crow::black_magic::is_equ_p(str, "MOVE", 4) ? crow::HTTPMethod::Move : + crow::black_magic::is_equ_p(str, "PROPFIND", 8) ? crow::HTTPMethod::Propfind : + crow::black_magic::is_equ_p(str, "PROPPATCH", 9) ? crow::HTTPMethod::Proppatch : + crow::black_magic::is_equ_p(str, "SEARCH", 6) ? crow::HTTPMethod::Search : + crow::black_magic::is_equ_p(str, "UNLOCK", 6) ? crow::HTTPMethod::Unlock : + crow::black_magic::is_equ_p(str, "BIND", 4) ? crow::HTTPMethod::Bind : + crow::black_magic::is_equ_p(str, "REBIND", 6) ? crow::HTTPMethod::Rebind : + crow::black_magic::is_equ_p(str, "UNBIND", 6) ? crow::HTTPMethod::Unbind : + crow::black_magic::is_equ_p(str, "ACL", 3) ? crow::HTTPMethod::Acl : + + crow::black_magic::is_equ_p(str, "REPORT", 6) ? crow::HTTPMethod::Report : + crow::black_magic::is_equ_p(str, "MKACTIVITY", 10) ? crow::HTTPMethod::MkActivity : + crow::black_magic::is_equ_p(str, "CHECKOUT", 8) ? crow::HTTPMethod::Checkout : + crow::black_magic::is_equ_p(str, "MERGE", 5) ? crow::HTTPMethod::Merge : + + crow::black_magic::is_equ_p(str, "MSEARCH", 7) ? crow::HTTPMethod::MSearch : + crow::black_magic::is_equ_p(str, "NOTIFY", 6) ? crow::HTTPMethod::Notify : + crow::black_magic::is_equ_p(str, "SUBSCRIBE", 9) ? crow::HTTPMethod::Subscribe : + crow::black_magic::is_equ_p(str, "UNSUBSCRIBE", 11) ? crow::HTTPMethod::Unsubscribe : + + crow::black_magic::is_equ_p(str, "MKCALENDAR", 10) ? crow::HTTPMethod::MkCalendar : + + crow::black_magic::is_equ_p(str, "LINK", 4) ? crow::HTTPMethod::Link : + crow::black_magic::is_equ_p(str, "UNLINK", 6) ? crow::HTTPMethod::Unlink : + + crow::black_magic::is_equ_p(str, "SOURCE", 6) ? crow::HTTPMethod::Source : + throw std::runtime_error("invalid http method"); +} + +constexpr crow::HTTPMethod operator"" _method(const char* str, size_t /*len*/) +{ + return method_from_string( str ); +} +#endif +// clang-format on + + +#ifdef CROW_USE_BOOST +#include +#else +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif +#include +#endif + + +namespace crow // NOTE: Already documented in "crow/app.h" +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; +#endif + + /// Find and return the value associated with the key. (returns an empty string if nothing is found) + template + inline const std::string& get_header_value(const T& headers, const std::string& key) + { + if (headers.count(key)) + { + return headers.find(key)->second; + } + static std::string empty; + return empty; + } + + /// An HTTP request. + struct request + { + HTTPMethod method; + std::string raw_url; ///< The full URL containing the `?` and URL parameters. + std::string url; ///< The endpoint without any parameters. + query_string url_params; ///< The parameters associated with the request. (everything after the `?` in the URL) + ci_map headers; + std::string body; + std::string remote_ip_address; ///< The IP address from which the request was sent. + unsigned char http_ver_major, http_ver_minor; + bool keep_alive, ///< Whether or not the server should send a `connection: Keep-Alive` header to the client. + close_connection, ///< Whether or not the server should shut down the TCP connection once a response is sent. + upgrade; ///< Whether or noth the server should change the HTTP connection to a different connection. + + void* middleware_context{}; + void* middleware_container{}; + asio::io_service* io_service{}; + + /// Construct an empty request. (sets the method to `GET`) + request(): + method(HTTPMethod::Get) + {} + + /// Construct a request with all values assigned. + request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body, unsigned char http_major, unsigned char http_minor, bool has_keep_alive, bool has_close_connection, bool is_upgrade): + method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body)), http_ver_major(http_major), http_ver_minor(http_minor), keep_alive(has_keep_alive), close_connection(has_close_connection), upgrade(is_upgrade) + {} + + void add_header(std::string key, std::string value) + { + headers.emplace(std::move(key), std::move(value)); + } + + const std::string& get_header_value(const std::string& key) const + { + return crow::get_header_value(headers, key); + } + + bool check_version(unsigned char major, unsigned char minor) const + { + return http_ver_major == major && http_ver_minor == minor; + } + + /// Get the body as parameters in QS format. + + /// + /// This is meant to be used with requests of type "application/x-www-form-urlencoded" + const query_string get_body_params() const + { + return query_string(body, false); + } + + /// Send data to whoever made this request with a completion handler and return immediately. + template + void post(CompletionHandler handler) + { + io_service->post(handler); + } + + /// Send data to whoever made this request with a completion handler. + template + void dispatch(CompletionHandler handler) + { + io_service->dispatch(handler); + } + }; +} // namespace crow + + +#include +#include +#include + + +namespace crow +{ + + /// Encapsulates anything related to processing and organizing `multipart/xyz` messages + namespace multipart + { + + const std::string dd = "--"; + + /// The first part in a section, contains metadata about the part + struct header + { + std::string value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition` + std::unordered_map params; ///< The parameters of the header, come after the `value` + + operator int() const { return std::stoi(value); } ///< Returns \ref value as integer + operator double() const { return std::stod(value); } ///< Returns \ref value as double + }; + + /// Multipart header map (key is header key). + using mph_map = std::unordered_multimap; + + /// Find and return the value object associated with the key. (returns an empty class if nothing is found) + template + inline const O& get_header_value_object(const T& headers, const std::string& key) + { + if (headers.count(key)) + { + return headers.find(key)->second; + } + static O empty; + return empty; + } + + /// Same as \ref get_header_value_object() but for \ref multipart.header + template + inline const header& get_header_object(const T& headers, const std::string& key) + { + return get_header_value_object
(headers, key); + } + + ///One part of the multipart message + + /// + /// It is usually separated from other sections by a `boundary` + struct part + { + mph_map headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding + std::string body; ///< The actual data in the part + + operator int() const { return std::stoi(body); } ///< Returns \ref body as integer + operator double() const { return std::stod(body); } ///< Returns \ref body as double + + const header& get_header_object(const std::string& key) const + { + return multipart::get_header_object(headers, key); + } + }; + + /// Multipart map (key is the name parameter). + using mp_map = std::unordered_multimap; + + /// The parsed multipart request/response + struct message : public returnable + { + ci_map headers; ///< The request/response headers + std::string boundary; ///< The text boundary that separates different `parts` + std::vector parts; ///< The individual parts of the message + mp_map part_map; ///< The individual parts of the message, organized in a map with the `name` header parameter being the key + + const std::string& get_header_value(const std::string& key) const + { + return crow::get_header_value(headers, key); + } + + part get_part_by_name(const std::string& name) + { + mp_map::iterator result = part_map.find(name); + if (result != part_map.end()) + return result->second; + else + return {}; + } + + /// Represent all parts as a string (**does not include message headers**) + std::string dump() const override + { + std::stringstream str; + std::string delimiter = dd + boundary; + + for (unsigned i = 0; i < parts.size(); i++) + { + str << delimiter << crlf; + str << dump(i); + } + str << delimiter << dd << crlf; + return str.str(); + } + + /// Represent an individual part as a string + std::string dump(int part_) const + { + std::stringstream str; + part item = parts[part_]; + for (auto& item_h : item.headers) + { + str << item_h.first << ": " << item_h.second.value; + for (auto& it : item_h.second.params) + { + str << "; " << it.first << '=' << pad(it.second); + } + str << crlf; + } + str << crlf; + str << item.body << crlf; + return str.str(); + } + + /// Default constructor using default values + message(const ci_map& headers, const std::string& boundary, const std::vector& sections): + returnable("multipart/form-data; boundary=CROW-BOUNDARY"), headers(headers), boundary(boundary), parts(sections) + { + if (!boundary.empty()) + content_type = "multipart/form-data; boundary=" + boundary; + for (auto& item : parts) + { + part_map.emplace( + (get_header_object(item.headers, "Content-Disposition").params.find("name")->second), + item); + } + } + + /// Create a multipart message from a request data + message(const request& req): + returnable("multipart/form-data; boundary=CROW-BOUNDARY"), + headers(req.headers), + boundary(get_boundary(get_header_value("Content-Type"))) + { + if (!boundary.empty()) + content_type = "multipart/form-data; boundary=" + boundary; + parse_body(req.body, parts, part_map); + } + + private: + std::string get_boundary(const std::string& header) const + { + constexpr char boundary_text[] = "boundary="; + size_t found = header.find(boundary_text); + if (found != std::string::npos) + { + std::string to_return(header.substr(found + strlen(boundary_text))); + if (to_return[0] == '\"') + { + to_return = to_return.substr(1, to_return.length() - 2); + } + return to_return; + } + return std::string(); + } + + void parse_body(std::string body, std::vector& sections, mp_map& part_map) + { + + std::string delimiter = dd + boundary; + + // TODO(EDev): Exit on error + while (body != (crlf)) + { + size_t found = body.find(delimiter); + if (found == std::string::npos) + { + // did not find delimiter; probably an ill-formed body; ignore the rest + break; + } + std::string section = body.substr(0, found); + + // +2 is the CRLF. + // We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF). + body.erase(0, found + delimiter.length() + 2); + if (!section.empty()) + { + part parsed_section(parse_section(section)); + part_map.emplace( + (get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second), + parsed_section); + sections.push_back(std::move(parsed_section)); + } + } + } + + part parse_section(std::string& section) + { + struct part to_return; + + size_t found = section.find(crlf + crlf); + std::string head_line = section.substr(0, found + 2); + section.erase(0, found + 4); + + parse_section_head(head_line, to_return); + to_return.body = section.substr(0, section.length() - 2); + return to_return; + } + + void parse_section_head(std::string& lines, part& part) + { + while (!lines.empty()) + { + header to_add; + + size_t found = lines.find(crlf); + std::string line = lines.substr(0, found); + std::string key; + lines.erase(0, found + 2); + // Add the header if available + if (!line.empty()) + { + size_t found = line.find("; "); + std::string header = line.substr(0, found); + if (found != std::string::npos) + line.erase(0, found + 2); + else + line = std::string(); + + size_t header_split = header.find(": "); + key = header.substr(0, header_split); + + to_add.value = header.substr(header_split + 2); + } + + // Add the parameters + while (!line.empty()) + { + size_t found = line.find("; "); + std::string param = line.substr(0, found); + if (found != std::string::npos) + line.erase(0, found + 2); + else + line = std::string(); + + size_t param_split = param.find('='); + + std::string value = param.substr(param_split + 1); + + to_add.params.emplace(param.substr(0, param_split), trim(value)); + } + part.headers.emplace(key, to_add); + } + } + + inline std::string trim(std::string& string, const char& excess = '"') const + { + if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess) + return string.substr(1, string.length() - 2); + return string; + } + + inline std::string pad(std::string& string, const char& padding = '"') const + { + return (padding + string + padding); + } + }; + } // namespace multipart +} // namespace crow + +/* merged revision: 5b951d74bd66ec9d38448e0a85b1cf8b85d97db3 */ +/* updated to : e13b274770da9b82a1085dec29182acfea72e7a7 (beyond v2.9.5) */ +/* commits not included: + * 091ebb87783a58b249062540bbea07de2a11e9cf + * 6132d1fefa03f769a3979355d1f5da0b8889cad2 + * 7ba312397c2a6c851a4b5efe6c1603b1e1bda6ff + * d7675453a6c03180572f084e95eea0d02df39164 + * dff604db203986e532e5a679bafd0e7382c6bdd9 (Might be useful to actually add [upgrade requests with a body]) + * e01811e7f4894d7f0f7f4bd8492cccec6f6b4038 (related to above) + * 05525c5fde1fc562481f6ae08fa7056185325daf (also related to above) + * 350258965909f249f9c59823aac240313e0d0120 (cannot be implemented due to upgrade) + */ + +// clang-format off +extern "C" { +#include +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9) +#include +#else +#include +#endif +#include +#include +#include +#include +} + +namespace crow +{ +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DCROW_HTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef CROW_HTTP_MAX_HEADER_SIZE +# define CROW_HTTP_MAX_HEADER_SIZE (80*1024) +#endif + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Flag values for http_parser.flags field */ +enum http_connection_flags // This is basically 7 booleans placed into 1 integer. Uses 4 bytes instead of n bytes (7 currently). + { F_CHUNKED = 1 << 0 // 00000000 00000000 00000000 00000001 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 // 00000000 00000000 00000000 00000010 + , F_CONNECTION_CLOSE = 1 << 2 // 00000000 00000000 00000000 00000100 + , F_TRAILING = 1 << 3 // 00000000 00000000 00000000 00001000 + , F_UPGRADE = 1 << 4 // 00000000 00000000 00000000 00010000 + , F_SKIPBODY = 1 << 5 // 00000000 00000000 00000000 00100000 + , F_CONTENTLENGTH = 1 << 6 // 00000000 00000000 00000000 01000000 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define CROW_HTTP_ERRNO_MAP(CROW_XX) \ + /* No error */ \ + CROW_XX(OK, "success") \ + \ + /* Callback-related errors */ \ + CROW_XX(CB_message_begin, "the on_message_begin callback failed") \ + CROW_XX(CB_method, "the on_method callback failed") \ + CROW_XX(CB_url, "the \"on_url\" callback failed") \ + CROW_XX(CB_header_field, "the \"on_header_field\" callback failed") \ + CROW_XX(CB_header_value, "the \"on_header_value\" callback failed") \ + CROW_XX(CB_headers_complete, "the \"on_headers_complete\" callback failed") \ + CROW_XX(CB_body, "the \"on_body\" callback failed") \ + CROW_XX(CB_message_complete, "the \"on_message_complete\" callback failed") \ + CROW_XX(CB_status, "the \"on_status\" callback failed") \ + \ + /* Parsing-related errors */ \ + CROW_XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + CROW_XX(HEADER_OVERFLOW, "too many header bytes seen; overflow detected") \ + CROW_XX(CLOSED_CONNECTION, "data received after completed connection: close message") \ + CROW_XX(INVALID_VERSION, "invalid HTTP version") \ + CROW_XX(INVALID_STATUS, "invalid HTTP status code") \ + CROW_XX(INVALID_METHOD, "invalid HTTP method") \ + CROW_XX(INVALID_URL, "invalid URL") \ + CROW_XX(INVALID_HOST, "invalid host") \ + CROW_XX(INVALID_PORT, "invalid port") \ + CROW_XX(INVALID_PATH, "invalid path") \ + CROW_XX(INVALID_QUERY_STRING, "invalid query string") \ + CROW_XX(INVALID_FRAGMENT, "invalid fragment") \ + CROW_XX(LF_EXPECTED, "LF character expected") \ + CROW_XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + CROW_XX(INVALID_CONTENT_LENGTH, "invalid character in content-length header") \ + CROW_XX(UNEXPECTED_CONTENT_LENGTH, "unexpected content-length header") \ + CROW_XX(INVALID_CHUNK_SIZE, "invalid character in chunk size header") \ + CROW_XX(INVALID_CONSTANT, "invalid constant string") \ + CROW_XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state") \ + CROW_XX(STRICT, "strict mode assertion failed") \ + CROW_XX(UNKNOWN, "an unknown error occurred") \ + CROW_XX(INVALID_TRANSFER_ENCODING, "request has invalid transfer-encoding") \ + + +/* Define CHPE_* values for each errno value above */ +#define CROW_HTTP_ERRNO_GEN(n, s) CHPE_##n, +enum http_errno { + CROW_HTTP_ERRNO_MAP(CROW_HTTP_ERRNO_GEN) +}; +#undef CROW_HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define CROW_HTTP_PARSER_ERRNO(p) ((enum http_errno)(p)->http_errno) + + + struct http_parser + { + /** PRIVATE **/ + unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 8; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 5; /* index into current matcher */ + unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */ + unsigned int allow_chunked_length : 1; /* Allow headers with both `Content-Length` and `Transfer-Encoding: chunked` set */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) if no Content-Length header. */ + unsigned long qs_point; + + /** READ-ONLY **/ + unsigned char http_major; + unsigned char http_minor; + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void* data; /* A pointer to get hook to the "connection" or "socket" object */ + }; + + + struct http_parser_settings + { + http_cb on_message_begin; + http_cb on_method; + http_data_cb on_url; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + }; + + + +// SOURCE (.c) CODE +static uint32_t max_header_size = CROW_HTTP_MAX_HEADER_SIZE; + +#ifndef CROW_ULLONG_MAX +# define CROW_ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef CROW_MIN +# define CROW_MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef CROW_ARRAY_SIZE +# define CROW_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef CROW_BIT_AT +# define CROW_BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#define CROW_SET_ERRNO(e) \ +do { \ + parser->nread = nread; \ + parser->http_errno = (e); \ +} while(0) + +/* Run the notify callback FOR, returning ER if it fails */ +#define CROW_CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK); \ + \ + if (CROW_LIKELY(settings->on_##FOR)) { \ + if (CROW_UNLIKELY(0 != settings->on_##FOR(parser))) { \ + CROW_SET_ERRNO(CHPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (CROW_UNLIKELY(CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK)) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CROW_CALLBACK_NOTIFY(FOR) CROW_CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CROW_CALLBACK_NOTIFY_NOADVANCE(FOR) CROW_CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CROW_CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK); \ + \ + if (FOR##_mark) { \ + if (CROW_LIKELY(settings->on_##FOR)) { \ + if (CROW_UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + CROW_SET_ERRNO(CHPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (CROW_UNLIKELY(CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK)) {\ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CROW_CALLBACK_DATA(FOR) \ + CROW_CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CROW_CALLBACK_DATA_NOADVANCE(FOR) \ + CROW_CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define CROW_MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed max_header_size. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. max_header_size is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define CROW_COUNT_HEADER_SIZE(V) \ +do { \ + nread += (uint32_t)(V); \ + if (CROW_UNLIKELY(nread > max_header_size)) { \ + CROW_SET_ERRNO(CHPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) +#define CROW_REEXECUTE() \ + goto reexecute; \ + +#define CROW_PROXY_CONNECTION "proxy-connection" +#define CROW_CONNECTION "connection" +#define CROW_CONTENT_LENGTH "content-length" +#define CROW_TRANSFER_ENCODING "transfer-encoding" +#define CROW_UPGRADE "upgrade" +#define CROW_CHUNKED "chunked" +#define CROW_KEEP_ALIVE "keep-alive" +#define CROW_CLOSE "close" + + + + enum state + { + s_dead = 1 /* important that this is > 0 */ + + , + s_start_req + + , + s_req_method, + s_req_spaces_before_url, + s_req_schema, + s_req_schema_slash, + s_req_schema_slash_slash, + s_req_server_start, + s_req_server, // } + s_req_server_with_at, // | + s_req_path, // | The parser recognizes how to switch between these states, + s_req_query_string_start, // | however it doesn't process them any differently. + s_req_query_string, // } + s_req_http_start, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_http_I, + s_req_http_IC, + s_req_http_major, + s_req_http_dot, + s_req_http_minor, + s_req_http_end, + s_req_line_almost_done + + , + s_header_field_start, + s_header_field, + s_header_value_discard_ws, + s_header_value_discard_ws_almost_done, + s_header_value_discard_lws, + s_header_value_start, + s_header_value, + s_header_value_lws + + , + s_header_almost_done + + , + s_chunk_size_start, + s_chunk_size, + s_chunk_parameters, + s_chunk_size_almost_done + + , + s_headers_almost_done, + s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the CROW_PARSING_HEADER() macro. + */ + + , + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done + + , + s_body_identity, + s_body_identity_eof + + , + s_message_done + }; + + +#define CROW_PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_content_length_num + , h_content_length_ws + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_token_start + , h_matching_transfer_encoding_chunked + , h_matching_transfer_encoding_token + + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CROW_LOWER(c) (unsigned char)(c | 0x20) +#define CROW_IS_ALPHA(c) (CROW_LOWER(c) >= 'a' && CROW_LOWER(c) <= 'z') +#define CROW_IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define CROW_IS_ALPHANUM(c) (CROW_IS_ALPHA(c) || CROW_IS_NUM(c)) +//#define CROW_IS_HEX(c) (CROW_IS_NUM(c) || (CROW_LOWER(c) >= 'a' && CROW_LOWER(c) <= 'f')) +#define CROW_IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define CROW_IS_USERINFO_CHAR(c) (CROW_IS_ALPHANUM(c) || CROW_IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define CROW_TOKEN(c) (tokens[(unsigned char)c]) +#define CROW_IS_URL_CHAR(c) (CROW_BIT_AT(normal_url_char, (unsigned char)c)) +//#define CROW_IS_HOST_CHAR(c) (CROW_IS_ALPHANUM(c) || (c) == '.' || (c) == '-') + + /** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define CROW_IS_HEADER_CHAR(ch) \ + (ch == cr || ch == lf || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#define CROW_start_state s_start_req + +# define CROW_STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + CROW_SET_ERRNO(CHPE_STRICT); \ + goto error; \ + } \ +} while (0) +#define CROW_NEW_MESSAGE() (CROW_start_state) + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +inline enum state +parse_url_char(enum state s, const char ch, http_parser *parser, const char* url_mark, const char* p) +{ +# define CROW_T(v) 0 + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 |CROW_T(2)| 0 | 0 |CROW_T(16)| 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 CROW_T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef CROW_T + + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + if (ch == '\t' || ch == '\f') { + return s_dead; + } + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (CROW_IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (CROW_IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* fall through */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + parser->qs_point = p - url_mark; + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (CROW_IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (CROW_IS_URL_CHAR(ch)) { + return s; + } + else if (ch == '?') + { + parser->qs_point = p - url_mark; + return s_req_query_string_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (CROW_IS_URL_CHAR(ch)) { + return s_req_query_string; + } + else if (ch == '?') + { + return s_req_query_string; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +inline size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + + + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *url_start_mark = 0; + const char *body_mark = 0; + const unsigned int lenient = parser->lenient_http_headers; + const unsigned int allow_chunked_length = parser->allow_chunked_length; + + uint32_t nread = parser->nread; + + /* We're in an error state. Don't bother doing anything. */ + if (CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK) { + return 0; + } + + if (len == 0) { + switch (parser->state) { + case s_body_identity_eof: + /* Use of CROW_CALLBACK_NOTIFY() here would erroneously return 1 byte read if we got paused. */ + CROW_CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req: + return 0; + + default: + CROW_SET_ERRNO(CHPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (parser->state == s_header_field) + header_field_mark = data; + if (parser->state == s_header_value) + header_value_mark = data; + switch (parser->state) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + url_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (CROW_PARSING_HEADER(parser->state)) + CROW_COUNT_HEADER_SIZE(1); + +reexecute: + switch (parser->state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (CROW_LIKELY(ch == cr || ch == lf)) + break; + + CROW_SET_ERRNO(CHPE_CLOSED_CONNECTION); + goto error; + + case s_start_req: + { + if (ch == cr || ch == lf) + break; + parser->flags = 0; + parser->uses_transfer_encoding = 0; + parser->content_length = CROW_ULLONG_MAX; + + if (CROW_UNLIKELY(!CROW_IS_ALPHA(ch))) { + CROW_SET_ERRNO(CHPE_INVALID_METHOD); + goto error; + } + + parser->method = 0; + parser->index = 1; + switch (ch) { + case 'A': parser->method = (unsigned)HTTPMethod::Acl; break; + case 'B': parser->method = (unsigned)HTTPMethod::Bind; break; + case 'C': parser->method = (unsigned)HTTPMethod::Connect; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = (unsigned)HTTPMethod::Delete; break; + case 'G': parser->method = (unsigned)HTTPMethod::Get; break; + case 'H': parser->method = (unsigned)HTTPMethod::Head; break; + case 'L': parser->method = (unsigned)HTTPMethod::Lock; /* or LINK */ break; + case 'M': parser->method = (unsigned)HTTPMethod::MkCol; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = (unsigned)HTTPMethod::Notify; break; + case 'O': parser->method = (unsigned)HTTPMethod::Options; break; + case 'P': parser->method = (unsigned)HTTPMethod::Post; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; + case 'R': parser->method = (unsigned)HTTPMethod::Report; /* or REBIND */ break; + case 'S': parser->method = (unsigned)HTTPMethod::Subscribe; /* or SEARCH, SOURCE */ break; + case 'T': parser->method = (unsigned)HTTPMethod::Trace; break; + case 'U': parser->method = (unsigned)HTTPMethod::Unlock; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; + default: + CROW_SET_ERRNO(CHPE_INVALID_METHOD); + goto error; + } + parser->state = s_req_method; + + CROW_CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (CROW_UNLIKELY(ch == '\0')) { + CROW_SET_ERRNO(CHPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') { + + switch (parser->method << 16 | parser->index << 8 | ch) { +#define CROW_XX(meth, pos, ch, new_meth) \ + case ((unsigned)HTTPMethod::meth << 16 | pos << 8 | ch): \ + parser->method = (unsigned)HTTPMethod::new_meth; break; + + CROW_XX(Post, 1, 'U', Put) + CROW_XX(Post, 1, 'A', Patch) + CROW_XX(Post, 1, 'R', Propfind) + CROW_XX(Put, 2, 'R', Purge) + CROW_XX(Connect, 1, 'H', Checkout) + CROW_XX(Connect, 2, 'P', Copy) + CROW_XX(MkCol, 1, 'O', Move) + CROW_XX(MkCol, 1, 'E', Merge) + CROW_XX(MkCol, 1, '-', MSearch) + CROW_XX(MkCol, 2, 'A', MkActivity) + CROW_XX(MkCol, 3, 'A', MkCalendar) + CROW_XX(Subscribe, 1, 'E', Search) + CROW_XX(Subscribe, 1, 'O', Source) + CROW_XX(Report, 2, 'B', Rebind) + CROW_XX(Propfind, 4, 'P', Proppatch) + CROW_XX(Lock, 1, 'I', Link) + CROW_XX(Unlock, 2, 'S', Unsubscribe) + CROW_XX(Unlock, 2, 'B', Unbind) + CROW_XX(Unlock, 3, 'I', Unlink) +#undef CROW_XX + default: + CROW_SET_ERRNO(CHPE_INVALID_METHOD); + goto error; + } + } else { + CROW_SET_ERRNO(CHPE_INVALID_METHOD); + goto error; + } + + CROW_CALLBACK_NOTIFY_NOADVANCE(method); + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + CROW_MARK(url); + CROW_MARK(url_start); + if (parser->method == (unsigned)HTTPMethod::Connect) { + parser->state = s_req_server_start; + } + + parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p); + if (CROW_UNLIKELY(parser->state == s_dead)) { + CROW_SET_ERRNO(CHPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case cr: + case lf: + CROW_SET_ERRNO(CHPE_INVALID_URL); + goto error; + default: + parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p); + if (CROW_UNLIKELY(parser->state == s_dead)) { + CROW_SET_ERRNO(CHPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + { + switch (ch) { + case ' ': + parser->state = s_req_http_start; + CROW_CALLBACK_DATA(url); + break; + case cr: // No space after URL means no HTTP version. Which means the request is using HTTP/0.9 + case lf: + if (CROW_UNLIKELY(parser->method != (unsigned)HTTPMethod::Get)) // HTTP/0.9 doesn't define any method other than GET + { + parser->state = s_dead; + CROW_SET_ERRNO(CHPE_INVALID_VERSION); + goto error; + } + parser->http_major = 0; + parser->http_minor = 9; + parser->state = (ch == cr) ? + s_req_line_almost_done : + s_header_field_start; + CROW_CALLBACK_DATA(url); + break; + default: + parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p); + if (CROW_UNLIKELY(parser->state == s_dead)) { + CROW_SET_ERRNO(CHPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case ' ': + break; + case 'H': + parser->state = s_req_http_H; + break; + case 'I': + if (parser->method == (unsigned)HTTPMethod::Source) { + parser->state = s_req_http_I; + break; + } + /* fall through */ + default: + CROW_SET_ERRNO(CHPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + CROW_STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HT; + break; + + case s_req_http_HT: + CROW_STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HTT; + break; + + case s_req_http_HTT: + CROW_STRICT_CHECK(ch != 'P'); + parser->state = s_req_http_HTTP; + break; + + case s_req_http_I: + CROW_STRICT_CHECK(ch != 'C'); + parser->state = s_req_http_IC; + break; + + case s_req_http_IC: + CROW_STRICT_CHECK(ch != 'E'); + parser->state = s_req_http_HTTP; /* Treat "ICE" as "HTTP". */ + break; + + case s_req_http_HTTP: + CROW_STRICT_CHECK(ch != '/'); + parser->state = s_req_http_major; + break; + + /* dot */ + case s_req_http_major: + if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) { + CROW_SET_ERRNO(CHPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_req_http_dot; + break; + + case s_req_http_dot: + { + if (CROW_UNLIKELY(ch != '.')) { + CROW_SET_ERRNO(CHPE_INVALID_VERSION); + goto error; + } + + parser->state = s_req_http_minor; + break; + } + + /* minor HTTP version */ + case s_req_http_minor: + if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) { + CROW_SET_ERRNO(CHPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_req_http_end; + break; + + /* end of request line */ + case s_req_http_end: + { + if (ch == cr) { + parser->state = s_req_line_almost_done; + break; + } + + if (ch == lf) { + parser->state = s_header_field_start; + break; + } + + CROW_SET_ERRNO(CHPE_INVALID_VERSION); + goto error; + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (CROW_UNLIKELY(ch != lf)) { + CROW_SET_ERRNO(CHPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == cr) { + parser->state = s_headers_almost_done; + break; + } + + if (ch == lf) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + parser->state = s_headers_almost_done; + CROW_REEXECUTE(); + } + + c = CROW_TOKEN(ch); + + if (CROW_UNLIKELY(!c)) { + CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN); + goto error; + } + + CROW_MARK(header_field); + + parser->index = 0; + parser->state = s_header_field; + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = CROW_TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: { + size_t left = data + len - p; + const char* pe = p + CROW_MIN(left, max_header_size); + while (p+1 < pe && CROW_TOKEN(p[1])) { + p++; + } + break; + } + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CROW_CONNECTION)-1 || c != CROW_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CROW_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(CROW_PROXY_CONNECTION)-1 || c != CROW_PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CROW_PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CROW_CONTENT_LENGTH)-1 || c != CROW_CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CROW_CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(CROW_TRANSFER_ENCODING)-1 || c != CROW_TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CROW_TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + parser->uses_transfer_encoding = 1; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(CROW_UPGRADE)-1 || c != CROW_UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CROW_UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + if (p == data + len) { + --p; + CROW_COUNT_HEADER_SIZE(p - start); + break; + } + + CROW_COUNT_HEADER_SIZE(p - start); + + if (ch == ':') { + parser->state = s_header_value_discard_ws; + CROW_CALLBACK_DATA(header_field); + break; + } +/* RFC-7230 Sec 3.2.4 expressly forbids line-folding in header field-names. + if (ch == cr) { + parser->state = s_header_almost_done; + CROW_CALLBACK_DATA(header_field); + break; + } + + if (ch == lf) { + parser->state = s_header_field_start; + CROW_CALLBACK_DATA(header_field); + break; + } +*/ + CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == cr) { + parser->state = s_header_value_discard_ws_almost_done; + break; + } + + if (ch == lf) { + parser->state = s_header_value_discard_lws; + break; + } + + /* fall through */ + + case s_header_value_start: + { + CROW_MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + + c = CROW_LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + // Crow does not support HTTP/2 at the moment. + // According to the RFC https://datatracker.ietf.org/doc/html/rfc7540#section-3.2 + // "A server that does not support HTTP/2 can respond to the request as though the Upgrade header field were absent" + // => `F_UPGRADE` is not set if the header starts by "h2". + // This prevents the parser from skipping the request body. + if (ch != 'h' || p+1 == (data + len) || *(p+1) != '2') { + parser->flags |= F_UPGRADE; + } + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_matching_transfer_encoding_token; + } + break; + + /* Multi-value `Transfer-Encoding` header */ + case h_matching_transfer_encoding_token_start: + break; + + case h_content_length: + if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) { + CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH); + goto error; + } + + if (parser->flags & F_CONTENTLENGTH) { + CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + parser->flags |= F_CONTENTLENGTH; + parser->content_length = ch - '0'; + parser->header_state = h_content_length_num; + break; + + /* when obsolete line folding is encountered for content length + * continue to the s_header_value state */ + case h_content_length_ws: + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + parser->header_state = h_general; + } + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = static_cast(parser->header_state); + for (; p != data + len; p++) { + ch = *p; + + if (ch == cr) { + parser->state = s_header_almost_done; + parser->header_state = h_state; + CROW_CALLBACK_DATA(header_value); + break; + } + + if (ch == lf) { + parser->state = s_header_almost_done; + CROW_COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CROW_CALLBACK_DATA_NOADVANCE(header_value); + CROW_REEXECUTE(); + } + + if (!lenient && !CROW_IS_HEADER_CHAR(ch)) { + CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = CROW_LOWER(ch); + + switch (h_state) { + case h_general: + { + size_t left = data + len - p; + const char* pe = p + CROW_MIN(left, max_header_size); + + for (; p != pe; p++) { + ch = *p; + if (ch == cr || ch == lf) { + --p; + break; + } + if (!lenient && !CROW_IS_HEADER_CHAR(ch)) { + CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN); + goto error; + } + } + if (p == data + len) + --p; + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + if (ch == ' ') break; + h_state = h_content_length_num; + /* fall through */ + + case h_content_length_num: + { + uint64_t t; + + if (ch == ' ') { + h_state = h_content_length_ws; + break; + } + + if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) { + CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (CROW_UNLIKELY((CROW_ULLONG_MAX - 10) / 10 < parser->content_length)) { + CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + case h_content_length_ws: + if (ch == ' ') break; + CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_token_start: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + h_state = h_matching_transfer_encoding_chunked; + } else if (CROW_TOKEN(c)) { + /* TODO(indutny): similar code below does this, but why? + * At the very least it seems to be inconsistent given that + * h_matching_transfer_encoding_token does not check for + * `STRICT_TOKEN` + */ + h_state = h_matching_transfer_encoding_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CROW_CHUNKED)-1 || c != CROW_CHUNKED[parser->index]) { + h_state = h_matching_transfer_encoding_token; + } else if (parser->index == sizeof(CROW_CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_transfer_encoding_token: + if (ch == ',') { + h_state = h_matching_transfer_encoding_token_start; + parser->index = 0; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(CROW_KEEP_ALIVE)-1 || c != CROW_KEEP_ALIVE[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CROW_KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CROW_CLOSE)-1 || c != CROW_CLOSE[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CROW_CLOSE)-2) { + h_state = h_connection_close; + } + break; + + // Edited from original (because of commits that werent included) + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_matching_transfer_encoding_token; + break; + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') h_state = h_general; + break; + + default: + parser->state = s_header_value; + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + + if (p == data + len) + --p; + + CROW_COUNT_HEADER_SIZE(p - start); + break; + } + + case s_header_almost_done: + { + if (CROW_UNLIKELY(ch != lf)) { + CROW_SET_ERRNO(CHPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_value_lws; + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + if (parser->header_state == h_content_length_num) { + /* treat obsolete line folding as space */ + parser->header_state = h_content_length_ws; + } + parser->state = s_header_value_start; + CROW_REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + parser->state = s_header_field_start; + CROW_REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + CROW_STRICT_CHECK(ch != lf); + parser->state = s_header_value_discard_lws; + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + parser->state = s_header_value_discard_ws; + break; + } else { + /* header value was empty */ + CROW_MARK(header_value); + parser->state = s_header_field_start; + CROW_CALLBACK_DATA_NOADVANCE(header_value); + CROW_REEXECUTE(); + } + } + + case s_headers_almost_done: + { + CROW_STRICT_CHECK(ch != lf); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + CROW_CALLBACK_NOTIFY(message_complete); + break; + } + + /* Cannot use transfer-encoding and a content-length header together + per the HTTP specification. (RFC 7230 Section 3.3.3) */ + if ((parser->uses_transfer_encoding == 1) && + (parser->flags & F_CONTENTLENGTH)) { + /* Allow it for lenient parsing as long as `Transfer-Encoding` is + * not `chunked` or allow_length_with_encoding is set + */ + if (parser->flags & F_CHUNKED) { + if (!allow_chunked_length) { + CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + } else if (!lenient) { + CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + } + + parser->state = s_headers_done; + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + (parser->flags & F_UPGRADE || parser->method == (unsigned)HTTPMethod::Connect); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CROW_CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + //break; + + /* fall through */ + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + CROW_SET_ERRNO(CHPE_CB_headers_complete); + parser->nread = nread; + return p - data; /* Error */ + } + } + + if (CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK) { + parser->nread = nread; + return p - data; + } + + CROW_REEXECUTE(); + } + + case s_headers_done: + { + CROW_STRICT_CHECK(ch != lf); + + parser->nread = 0; + nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + CROW_CALLBACK_NOTIFY(message_complete); + parser->nread = nread; + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + CROW_CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header, + * prepare for a chunk */ + parser->state = s_chunk_size_start; + } + else if (parser->uses_transfer_encoding == 1) + { + if (!lenient) + { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + CROW_SET_ERRNO(CHPE_INVALID_TRANSFER_ENCODING); + parser->nread = nread; + return (p - data); /* Error */ + } + else + { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + parser->state = s_body_identity_eof; + } + } + else + { + if (parser->content_length == 0) + { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + CROW_CALLBACK_NOTIFY(message_complete); + } + else if (parser->content_length != CROW_ULLONG_MAX) + { + /* Content-Length header given and non-zero */ + parser->state = s_body_identity; + } + else + { + /* Assume content-length 0 - read the next */ + CROW_CALLBACK_NOTIFY(message_complete); + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = CROW_MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != CROW_ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + CROW_MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CROW_CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CROW_CALLBACK_DATA_(body, p - body_mark + 1, p - data); + CROW_REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + CROW_MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + CROW_CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[static_cast(ch)]; + if (CROW_UNLIKELY(unhex_val == -1)) { + CROW_SET_ERRNO(CHPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + parser->state = s_chunk_size; + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == cr) { + parser->state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[static_cast(ch)]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + parser->state = s_chunk_parameters; + break; + } + + CROW_SET_ERRNO(CHPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (CROW_UNLIKELY((CROW_ULLONG_MAX - 16) / 16 < parser->content_length)) { + CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == cr) { + parser->state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + CROW_STRICT_CHECK(ch != lf); + + parser->nread = 0; + nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + parser->state = s_header_field_start; + } else { + parser->state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = CROW_MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != CROW_ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + CROW_MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + CROW_STRICT_CHECK(ch != cr); + parser->state = s_chunk_data_done; + CROW_CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + CROW_STRICT_CHECK(ch != lf); + parser->nread = 0; + nread = 0; + parser->state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + CROW_SET_ERRNO(CHPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran out of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CROW_CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0)) <= 1); + + CROW_CALLBACK_DATA_NOADVANCE(header_field); + CROW_CALLBACK_DATA_NOADVANCE(header_value); + CROW_CALLBACK_DATA_NOADVANCE(url); + CROW_CALLBACK_DATA_NOADVANCE(body); + + parser->nread = nread; + return len; + +error: + if (CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK) { + CROW_SET_ERRNO(CHPE_UNKNOWN); + } + + parser->nread = nread; + return (p - data); +} + +inline void + http_parser_init(http_parser* parser) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->state = s_start_req; + parser->http_errno = CHPE_OK; +} + +/* Return a string name of the given error */ +inline const char * +http_errno_name(enum http_errno err) { +/* Map errno values to strings for human-readable output */ +#define CROW_HTTP_STRERROR_GEN(n, s) { "CHPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + CROW_HTTP_ERRNO_MAP(CROW_HTTP_STRERROR_GEN) +}; +#undef CROW_HTTP_STRERROR_GEN + assert(((size_t) err) < CROW_ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +/* Return a string description of the given error */ +inline const char * +http_errno_description(enum http_errno err) { +/* Map errno values to strings for human-readable output */ +#define CROW_HTTP_STRERROR_GEN(n, s) { "CHPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + CROW_HTTP_ERRNO_MAP(CROW_HTTP_STRERROR_GEN) +}; +#undef CROW_HTTP_STRERROR_GEN + assert(((size_t) err) < CROW_ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +/* Checks if this is the final chunk of the body. */ +inline int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +/* Change the maximum header size provided at compile time. */ +inline void +http_parser_set_max_header_size(uint32_t size) { + max_header_size = size; +} + +#undef CROW_HTTP_ERRNO_MAP +#undef CROW_SET_ERRNO +#undef CROW_CALLBACK_NOTIFY_ +#undef CROW_CALLBACK_NOTIFY +#undef CROW_CALLBACK_NOTIFY_NOADVANCE +#undef CROW_CALLBACK_DATA_ +#undef CROW_CALLBACK_DATA +#undef CROW_CALLBACK_DATA_NOADVANCE +#undef CROW_MARK +#undef CROW_PROXY_CONNECTION +#undef CROW_CONNECTION +#undef CROW_CONTENT_LENGTH +#undef CROW_TRANSFER_ENCODING +#undef CROW_UPGRADE +#undef CROW_CHUNKED +#undef CROW_KEEP_ALIVE +#undef CROW_CLOSE +#undef CROW_PARSING_HEADER +#undef CROW_LOWER +#undef CROW_IS_ALPHA +#undef CROW_IS_NUM +#undef CROW_IS_ALPHANUM +//#undef CROW_IS_HEX +#undef CROW_IS_MARK +#undef CROW_IS_USERINFO_CHAR +#undef CROW_TOKEN +#undef CROW_IS_URL_CHAR +//#undef CROW_IS_HOST_CHAR +#undef CROW_STRICT_CHECK + +} + +// clang-format on + + +#include +#include +#include + + +namespace crow +{ + /// A wrapper for `nodejs/http-parser`. + + /// + /// Used to generate a \ref crow.request from the TCP socket buffer. + template + struct HTTPParser : public http_parser + { + static int on_message_begin(http_parser*) + { + return 0; + } + static int on_method(http_parser* self_) + { + HTTPParser* self = static_cast(self_); + self->req.method = static_cast(self->method); + + return 0; + } + static int on_url(http_parser* self_, const char* at, size_t length) + { + HTTPParser* self = static_cast(self_); + self->req.raw_url.insert(self->req.raw_url.end(), at, at + length); + self->req.url_params = query_string(self->req.raw_url); + self->req.url = self->req.raw_url.substr(0, self->qs_point != 0 ? self->qs_point : std::string::npos); + + self->process_url(); + + return 0; + } + static int on_header_field(http_parser* self_, const char* at, size_t length) + { + HTTPParser* self = static_cast(self_); + switch (self->header_building_state) + { + case 0: + if (!self->header_value.empty()) + { + self->req.headers.emplace(std::move(self->header_field), std::move(self->header_value)); + } + self->header_field.assign(at, at + length); + self->header_building_state = 1; + break; + case 1: + self->header_field.insert(self->header_field.end(), at, at + length); + break; + } + return 0; + } + static int on_header_value(http_parser* self_, const char* at, size_t length) + { + HTTPParser* self = static_cast(self_); + switch (self->header_building_state) + { + case 0: + self->header_value.insert(self->header_value.end(), at, at + length); + break; + case 1: + self->header_building_state = 0; + self->header_value.assign(at, at + length); + break; + } + return 0; + } + static int on_headers_complete(http_parser* self_) + { + HTTPParser* self = static_cast(self_); + if (!self->header_field.empty()) + { + self->req.headers.emplace(std::move(self->header_field), std::move(self->header_value)); + } + + self->set_connection_parameters(); + + self->process_header(); + return 0; + } + static int on_body(http_parser* self_, const char* at, size_t length) + { + HTTPParser* self = static_cast(self_); + self->req.body.insert(self->req.body.end(), at, at + length); + return 0; + } + static int on_message_complete(http_parser* self_) + { + HTTPParser* self = static_cast(self_); + + self->message_complete = true; + self->process_message(); + return 0; + } + HTTPParser(Handler* handler): + handler_(handler) + { + http_parser_init(this); + } + + // return false on error + /// Parse a buffer into the different sections of an HTTP request. + bool feed(const char* buffer, int length) + { + if (message_complete) + return true; + + const static http_parser_settings settings_{ + on_message_begin, + on_method, + on_url, + on_header_field, + on_header_value, + on_headers_complete, + on_body, + on_message_complete, + }; + + int nparsed = http_parser_execute(this, &settings_, buffer, length); + if (http_errno != CHPE_OK) + { + return false; + } + return nparsed == length; + } + + bool done() + { + return feed(nullptr, 0); + } + + void clear() + { + req = crow::request(); + header_field.clear(); + header_value.clear(); + header_building_state = 0; + qs_point = 0; + message_complete = false; + state = CROW_NEW_MESSAGE(); + } + + inline void process_url() + { + handler_->handle_url(); + } + + inline void process_header() + { + handler_->handle_header(); + } + + inline void process_message() + { + handler_->handle(); + } + + inline void set_connection_parameters() + { + req.http_ver_major = http_major; + req.http_ver_minor = http_minor; + + //NOTE(EDev): it seems that the problem is with crow's policy on closing the connection for HTTP_VERSION < 1.0, the behaviour for that in crow is "don't close the connection, but don't send a keep-alive either" + + // HTTP1.1 = always send keep_alive, HTTP1.0 = only send if header exists, HTTP?.? = never send + req.keep_alive = (http_major == 1 && http_minor == 0) ? + ((flags & F_CONNECTION_KEEP_ALIVE) ? true : false) : + ((http_major == 1 && http_minor == 1) ? true : false); + + // HTTP1.1 = only close if close header exists, HTTP1.0 = always close unless keep_alive header exists, HTTP?.?= never close + req.close_connection = (http_major == 1 && http_minor == 0) ? + ((flags & F_CONNECTION_KEEP_ALIVE) ? false : true) : + ((http_major == 1 && http_minor == 1) ? ((flags & F_CONNECTION_CLOSE) ? true : false) : false); + req.upgrade = static_cast(upgrade); + } + + /// The final request that this parser outputs. + /// + /// Data parsed is put directly into this object as soon as the related callback returns. (e.g. the request will have the cooorect method as soon as on_method() returns) + request req; + + private: + int header_building_state = 0; + bool message_complete = false; + std::string header_field; + std::string header_value; + + Handler* handler_; ///< This is currently an HTTP connection object (\ref crow.Connection). + }; +} // namespace crow + +#undef CROW_NEW_MESSAGE +#undef CROW_start_state + + + +#include +#include +#include +#include +#include +#include + +namespace crow +{ + enum class LogLevel + { +#ifndef ERROR +#ifndef DEBUG + DEBUG = 0, + INFO, + WARNING, + ERROR, + CRITICAL, +#endif +#endif + + Debug = 0, + Info, + Warning, + Error, + Critical, + }; + + class ILogHandler + { + public: + virtual ~ILogHandler() = default; + + virtual void log(std::string message, LogLevel level) = 0; + }; + + class CerrLogHandler : public ILogHandler + { + public: + void log(std::string message, LogLevel level) override + { + std::string prefix; + switch (level) + { + case LogLevel::Debug: + prefix = "DEBUG "; + break; + case LogLevel::Info: + prefix = "INFO "; + break; + case LogLevel::Warning: + prefix = "WARNING "; + break; + case LogLevel::Error: + prefix = "ERROR "; + break; + case LogLevel::Critical: + prefix = "CRITICAL"; + break; + } + std::cerr << std::string("(") + timestamp() + std::string(") [") + prefix + std::string("] ") + message << std::endl; + } + + private: + static std::string timestamp() + { + char date[32]; + time_t t = time(0); + + tm my_tm; + +#if defined(_MSC_VER) || defined(__MINGW32__) +#ifdef CROW_USE_LOCALTIMEZONE + localtime_s(&my_tm, &t); +#else + gmtime_s(&my_tm, &t); +#endif +#else +#ifdef CROW_USE_LOCALTIMEZONE + localtime_r(&t, &my_tm); +#else + gmtime_r(&t, &my_tm); +#endif +#endif + + size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm); + return std::string(date, date + sz); + } + }; + + class logger + { + public: + logger(LogLevel level): + level_(level) + {} + ~logger() + { +#ifdef CROW_ENABLE_LOGGING + if (level_ >= get_current_log_level()) + { + get_handler_ref()->log(stringstream_.str(), level_); + } +#endif + } + + // + template + logger& operator<<(T const& value) + { +#ifdef CROW_ENABLE_LOGGING + if (level_ >= get_current_log_level()) + { + stringstream_ << value; + } +#endif + return *this; + } + + // + static void setLogLevel(LogLevel level) { get_log_level_ref() = level; } + + static void setHandler(ILogHandler* handler) { get_handler_ref() = handler; } + + static LogLevel get_current_log_level() { return get_log_level_ref(); } + + private: + // + static LogLevel& get_log_level_ref() + { + static LogLevel current_level = static_cast(CROW_LOG_LEVEL); + return current_level; + } + static ILogHandler*& get_handler_ref() + { + static CerrLogHandler default_handler; + static ILogHandler* current_handler = &default_handler; + return current_handler; + } + + // + std::ostringstream stringstream_; + LogLevel level_; + }; +} // namespace crow + +#define CROW_LOG_CRITICAL \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \ + crow::logger(crow::LogLevel::Critical) +#define CROW_LOG_ERROR \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \ + crow::logger(crow::LogLevel::Error) +#define CROW_LOG_WARNING \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \ + crow::logger(crow::LogLevel::Warning) +#define CROW_LOG_INFO \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \ + crow::logger(crow::LogLevel::Info) +#define CROW_LOG_DEBUG \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \ + crow::logger(crow::LogLevel::Debug) + + +//#define CROW_JSON_NO_ERROR_CHECK +//#define CROW_JSON_USE_MAP + +#include +#ifdef CROW_JSON_USE_MAP +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + + +using std::isinf; +using std::isnan; + + +namespace crow // NOTE: Already documented in "crow/app.h" +{ + namespace mustache + { + class template_t; + } + + namespace json + { + inline void escape(const std::string& str, std::string& ret) + { + ret.reserve(ret.size() + str.size() + str.size() / 4); + for (auto c : str) + { + switch (c) + { + case '"': ret += "\\\""; break; + case '\\': ret += "\\\\"; break; + case '\n': ret += "\\n"; break; + case '\b': ret += "\\b"; break; + case '\f': ret += "\\f"; break; + case '\r': ret += "\\r"; break; + case '\t': ret += "\\t"; break; + default: + if (c >= 0 && c < 0x20) + { + ret += "\\u00"; + auto to_hex = [](char c) { + c = c & 0xf; + if (c < 10) + return '0' + c; + return 'a' + c - 10; + }; + ret += to_hex(c / 16); + ret += to_hex(c % 16); + } + else + ret += c; + break; + } + } + } + inline std::string escape(const std::string& str) + { + std::string ret; + escape(str, ret); + return ret; + } + + enum class type : char + { + Null, + False, + True, + Number, + String, + List, + Object, + Function + }; + + inline const char* get_type_str(type t) + { + switch (t) + { + case type::Number: return "Number"; + case type::False: return "False"; + case type::True: return "True"; + case type::List: return "List"; + case type::String: return "String"; + case type::Object: return "Object"; + case type::Function: return "Function"; + default: return "Unknown"; + } + } + + enum class num_type : char + { + Signed_integer, + Unsigned_integer, + Floating_point, + Null, + Double_precision_floating_point + }; + + class rvalue; + rvalue load(const char* data, size_t size); + + namespace detail + { + /// A read string implementation with comparison functionality. + struct r_string + { + r_string(){}; + r_string(char* s, char* e): + s_(s), e_(e){}; + ~r_string() + { + if (owned_) + delete[] s_; + } + + r_string(const r_string& r) + { + *this = r; + } + + r_string(r_string&& r) + { + *this = r; + } + + r_string& operator=(r_string&& r) + { + s_ = r.s_; + e_ = r.e_; + owned_ = r.owned_; + if (r.owned_) + r.owned_ = 0; + return *this; + } + + r_string& operator=(const r_string& r) + { + s_ = r.s_; + e_ = r.e_; + owned_ = 0; + return *this; + } + + operator std::string() const + { + return std::string(s_, e_); + } + + + const char* begin() const { return s_; } + const char* end() const { return e_; } + size_t size() const { return end() - begin(); } + + using iterator = const char*; + using const_iterator = const char*; + + char* s_; ///< Start. + mutable char* e_; ///< End. + uint8_t owned_{0}; + friend std::ostream& operator<<(std::ostream& os, const r_string& s) + { + os << static_cast(s); + return os; + } + + private: + void force(char* s, uint32_t length) + { + s_ = s; + e_ = s_ + length; + owned_ = 1; + } + friend rvalue crow::json::load(const char* data, size_t size); + + friend bool operator==(const r_string& l, const r_string& r); + friend bool operator==(const std::string& l, const r_string& r); + friend bool operator==(const r_string& l, const std::string& r); + + template + inline static bool equals(const T& l, const U& r) + { + if (l.size() != r.size()) + return false; + + for (size_t i = 0; i < l.size(); i++) + { + if (*(l.begin() + i) != *(r.begin() + i)) + return false; + } + + return true; + } + }; + + inline bool operator<(const r_string& l, const r_string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator<(const r_string& l, const std::string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator<(const std::string& l, const r_string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator>(const r_string& l, const r_string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator>(const r_string& l, const std::string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator>(const std::string& l, const r_string& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + + inline bool operator==(const r_string& l, const r_string& r) + { + return r_string::equals(l, r); + } + + inline bool operator==(const r_string& l, const std::string& r) + { + return r_string::equals(l, r); + } + + inline bool operator==(const std::string& l, const r_string& r) + { + return r_string::equals(l, r); + } + + inline bool operator!=(const r_string& l, const r_string& r) + { + return !(l == r); + } + + inline bool operator!=(const r_string& l, const std::string& r) + { + return !(l == r); + } + + inline bool operator!=(const std::string& l, const r_string& r) + { + return !(l == r); + } + } // namespace detail + + /// JSON read value. + + /// + /// Value can mean any json value, including a JSON object. + /// Read means this class is used to primarily read strings into a JSON value. + class rvalue + { + static const int cached_bit = 2; + static const int error_bit = 4; + + public: + rvalue() noexcept: + option_{error_bit} + { + } + rvalue(type t) noexcept: + lsize_{}, lremain_{}, t_{t} + { + } + rvalue(type t, char* s, char* e) noexcept: + start_{s}, end_{e}, t_{t} + { + determine_num_type(); + } + + rvalue(const rvalue& r): + start_(r.start_), end_(r.end_), key_(r.key_), t_(r.t_), nt_(r.nt_), option_(r.option_) + { + copy_l(r); + } + + rvalue(rvalue&& r) noexcept + { + *this = std::move(r); + } + + rvalue& operator=(const rvalue& r) + { + start_ = r.start_; + end_ = r.end_; + key_ = r.key_; + t_ = r.t_; + nt_ = r.nt_; + option_ = r.option_; + copy_l(r); + return *this; + } + rvalue& operator=(rvalue&& r) noexcept + { + start_ = r.start_; + end_ = r.end_; + key_ = std::move(r.key_); + l_ = std::move(r.l_); + lsize_ = r.lsize_; + lremain_ = r.lremain_; + t_ = r.t_; + nt_ = r.nt_; + option_ = r.option_; + return *this; + } + + explicit operator bool() const noexcept + { + return (option_ & error_bit) == 0; + } + + explicit operator int64_t() const + { + return i(); + } + + explicit operator uint64_t() const + { + return u(); + } + + explicit operator int() const + { + return static_cast(i()); + } + + /// Return any json value (not object or list) as a string. + explicit operator std::string() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() == type::Object || t() == type::List) + throw std::runtime_error("json type container"); +#endif + switch (t()) + { + case type::String: + return std::string(s()); + case type::Null: + return std::string("null"); + case type::True: + return std::string("true"); + case type::False: + return std::string("false"); + default: + return std::string(start_, end_ - start_); + } + } + + /// The type of the JSON value. + type t() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (option_ & error_bit) + { + throw std::runtime_error("invalid json object"); + } +#endif + return t_; + } + + /// The number type of the JSON value. + num_type nt() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (option_ & error_bit) + { + throw std::runtime_error("invalid json object"); + } +#endif + return nt_; + } + + /// The integer value. + int64_t i() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + switch (t()) + { + case type::Number: + case type::String: + return utility::lexical_cast(start_, end_ - start_); + default: + const std::string msg = "expected number, got: " + std::string(get_type_str(t())); + throw std::runtime_error(msg); + } +#endif + return utility::lexical_cast(start_, end_ - start_); + } + + /// The unsigned integer value. + uint64_t u() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + switch (t()) + { + case type::Number: + case type::String: + return utility::lexical_cast(start_, end_ - start_); + default: + throw std::runtime_error(std::string("expected number, got: ") + get_type_str(t())); + } +#endif + return utility::lexical_cast(start_, end_ - start_); + } + + /// The double precision floating-point number value. + double d() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Number) + throw std::runtime_error("value is not number"); +#endif + return utility::lexical_cast(start_, end_ - start_); + } + + /// The boolean value. + bool b() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::True && t() != type::False) + throw std::runtime_error("value is not boolean"); +#endif + return t() == type::True; + } + + /// The string value. + detail::r_string s() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::String) + throw std::runtime_error("value is not string"); +#endif + unescape(); + return detail::r_string{start_, end_}; + } + + /// The list or object value + std::vector lo() + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + std::vector ret; + ret.reserve(lsize_); + for (uint32_t i = 0; i < lsize_; i++) + { + ret.emplace_back(l_[i]); + } + return ret; + } + + /// Convert escaped string character to their original form ("\\n" -> '\n'). + void unescape() const + { + if (*(start_ - 1)) + { + char* head = start_; + char* tail = start_; + while (head != end_) + { + if (*head == '\\') + { + switch (*++head) + { + case '"': *tail++ = '"'; break; + case '\\': *tail++ = '\\'; break; + case '/': *tail++ = '/'; break; + case 'b': *tail++ = '\b'; break; + case 'f': *tail++ = '\f'; break; + case 'n': *tail++ = '\n'; break; + case 'r': *tail++ = '\r'; break; + case 't': *tail++ = '\t'; break; + case 'u': + { + auto from_hex = [](char c) { + if (c >= 'a') + return c - 'a' + 10; + if (c >= 'A') + return c - 'A' + 10; + return c - '0'; + }; + unsigned int code = + (from_hex(head[1]) << 12) + + (from_hex(head[2]) << 8) + + (from_hex(head[3]) << 4) + + from_hex(head[4]); + if (code >= 0x800) + { + *tail++ = 0xE0 | (code >> 12); + *tail++ = 0x80 | ((code >> 6) & 0x3F); + *tail++ = 0x80 | (code & 0x3F); + } + else if (code >= 0x80) + { + *tail++ = 0xC0 | (code >> 6); + *tail++ = 0x80 | (code & 0x3F); + } + else + { + *tail++ = code; + } + head += 4; + } + break; + } + } + else + *tail++ = *head; + head++; + } + end_ = tail; + *end_ = 0; + *(start_ - 1) = 0; + } + } + + /// Check if the json object has the passed string as a key. + bool has(const char* str) const + { + return has(std::string(str)); + } + + bool has(const std::string& str) const + { + struct Pred + { + bool operator()(const rvalue& l, const rvalue& r) const + { + return l.key_ < r.key_; + }; + bool operator()(const rvalue& l, const std::string& r) const + { + return l.key_ < r; + }; + bool operator()(const std::string& l, const rvalue& r) const + { + return l < r.key_; + }; + }; + if (!is_cached()) + { + std::sort(begin(), end(), Pred()); + set_cached(); + } + auto it = lower_bound(begin(), end(), str, Pred()); + return it != end() && it->key_ == str; + } + + int count(const std::string& str) + { + return has(str) ? 1 : 0; + } + + rvalue* begin() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return l_.get(); + } + rvalue* end() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return l_.get() + lsize_; + } + + const detail::r_string& key() const + { + return key_; + } + + size_t size() const + { + if (t() == type::String) + return s().size(); +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return lsize_; + } + + const rvalue& operator[](int index) const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::List) + throw std::runtime_error("value is not a list"); + if (index >= static_cast(lsize_) || index < 0) + throw std::runtime_error("list out of bound"); +#endif + return l_[index]; + } + + const rvalue& operator[](size_t index) const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::List) + throw std::runtime_error("value is not a list"); + if (index >= lsize_) + throw std::runtime_error("list out of bound"); +#endif + return l_[index]; + } + + const rvalue& operator[](const char* str) const + { + return this->operator[](std::string(str)); + } + + const rvalue& operator[](const std::string& str) const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object) + throw std::runtime_error("value is not an object"); +#endif + struct Pred + { + bool operator()(const rvalue& l, const rvalue& r) const + { + return l.key_ < r.key_; + }; + bool operator()(const rvalue& l, const std::string& r) const + { + return l.key_ < r; + }; + bool operator()(const std::string& l, const rvalue& r) const + { + return l < r.key_; + }; + }; + if (!is_cached()) + { + std::sort(begin(), end(), Pred()); + set_cached(); + } + auto it = lower_bound(begin(), end(), str, Pred()); + if (it != end() && it->key_ == str) + return *it; +#ifndef CROW_JSON_NO_ERROR_CHECK + throw std::runtime_error("cannot find key"); +#else + static rvalue nullValue; + return nullValue; +#endif + } + + void set_error() + { + option_ |= error_bit; + } + + bool error() const + { + return (option_ & error_bit) != 0; + } + + std::vector keys() const + { +#ifndef CROW_JSON_NO_ERROR_CHECK + if (t() != type::Object) + throw std::runtime_error("value is not an object"); +#endif + std::vector ret; + ret.reserve(lsize_); + for (uint32_t i = 0; i < lsize_; i++) + { + ret.emplace_back(std::string(l_[i].key())); + } + return ret; + } + + private: + bool is_cached() const + { + return (option_ & cached_bit) != 0; + } + void set_cached() const + { + option_ |= cached_bit; + } + void copy_l(const rvalue& r) + { + if (r.t() != type::Object && r.t() != type::List) + return; + lsize_ = r.lsize_; + lremain_ = 0; + l_.reset(new rvalue[lsize_]); + std::copy(r.begin(), r.end(), begin()); + } + + void emplace_back(rvalue&& v) + { + if (!lremain_) + { + int new_size = lsize_ + lsize_; + if (new_size - lsize_ > 60000) + new_size = lsize_ + 60000; + if (new_size < 4) + new_size = 4; + rvalue* p = new rvalue[new_size]; + rvalue* p2 = p; + for (auto& x : *this) + *p2++ = std::move(x); + l_.reset(p); + lremain_ = new_size - lsize_; + } + l_[lsize_++] = std::move(v); + lremain_--; + } + + /// Determines num_type from the string. + void determine_num_type() + { + if (t_ != type::Number) + { + nt_ = num_type::Null; + return; + } + + const std::size_t len = end_ - start_; + const bool has_minus = std::memchr(start_, '-', len) != nullptr; + const bool has_e = std::memchr(start_, 'e', len) != nullptr || std::memchr(start_, 'E', len) != nullptr; + const bool has_dec_sep = std::memchr(start_, '.', len) != nullptr; + if (has_dec_sep || has_e) + nt_ = num_type::Floating_point; + else if (has_minus) + nt_ = num_type::Signed_integer; + else + nt_ = num_type::Unsigned_integer; + } + + mutable char* start_; + mutable char* end_; + detail::r_string key_; + std::unique_ptr l_; + uint32_t lsize_; + uint16_t lremain_; + type t_; + num_type nt_{num_type::Null}; + mutable uint8_t option_{0}; + + friend rvalue load_nocopy_internal(char* data, size_t size); + friend rvalue load(const char* data, size_t size); + friend std::ostream& operator<<(std::ostream& os, const rvalue& r) + { + switch (r.t_) + { + + case type::Null: os << "null"; break; + case type::False: os << "false"; break; + case type::True: os << "true"; break; + case type::Number: + { + switch (r.nt()) + { + case num_type::Floating_point: os << r.d(); break; + case num_type::Double_precision_floating_point: os << r.d(); break; + case num_type::Signed_integer: os << r.i(); break; + case num_type::Unsigned_integer: os << r.u(); break; + case num_type::Null: throw std::runtime_error("Number with num_type Null"); + } + } + break; + case type::String: os << '"' << r.s() << '"'; break; + case type::List: + { + os << '['; + bool first = true; + for (auto& x : r) + { + if (!first) + os << ','; + first = false; + os << x; + } + os << ']'; + } + break; + case type::Object: + { + os << '{'; + bool first = true; + for (auto& x : r) + { + if (!first) + os << ','; + os << '"' << escape(x.key_) << "\":"; + first = false; + os << x; + } + os << '}'; + } + break; + case type::Function: os << "custom function"; break; + } + return os; + } + }; + namespace detail + { + } + + inline bool operator==(const rvalue& l, const std::string& r) + { + return l.s() == r; + } + + inline bool operator==(const std::string& l, const rvalue& r) + { + return l == r.s(); + } + + inline bool operator!=(const rvalue& l, const std::string& r) + { + return l.s() != r; + } + + inline bool operator!=(const std::string& l, const rvalue& r) + { + return l != r.s(); + } + + inline bool operator==(const rvalue& l, double r) + { + return l.d() == r; + } + + inline bool operator==(double l, const rvalue& r) + { + return l == r.d(); + } + + inline bool operator!=(const rvalue& l, double r) + { + return l.d() != r; + } + + inline bool operator!=(double l, const rvalue& r) + { + return l != r.d(); + } + + + inline rvalue load_nocopy_internal(char* data, size_t size) + { + // Defend against excessive recursion + static constexpr unsigned max_depth = 10000; + + //static const char* escaped = "\"\\/\b\f\n\r\t"; + struct Parser + { + Parser(char* data, size_t /*size*/): + data(data) + { + } + + bool consume(char c) + { + if (CROW_UNLIKELY(*data != c)) + return false; + data++; + return true; + } + + void ws_skip() + { + while (*data == ' ' || *data == '\t' || *data == '\r' || *data == '\n') + ++data; + }; + + rvalue decode_string() + { + if (CROW_UNLIKELY(!consume('"'))) + return {}; + char* start = data; + uint8_t has_escaping = 0; + while (1) + { + if (CROW_LIKELY(*data != '"' && *data != '\\' && *data != '\0')) + { + data++; + } + else if (*data == '"') + { + *data = 0; + *(start - 1) = has_escaping; + data++; + return {type::String, start, data - 1}; + } + else if (*data == '\\') + { + has_escaping = 1; + data++; + switch (*data) + { + case 'u': + { + auto check = [](char c) { + return ('0' <= c && c <= '9') || + ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'); + }; + if (!(check(*(data + 1)) && + check(*(data + 2)) && + check(*(data + 3)) && + check(*(data + 4)))) + return {}; + } + data += 5; + break; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + data++; + break; + default: + return {}; + } + } + else + return {}; + } + return {}; + } + + rvalue decode_list(unsigned depth) + { + rvalue ret(type::List); + if (CROW_UNLIKELY(!consume('[')) || CROW_UNLIKELY(depth > max_depth)) + { + ret.set_error(); + return ret; + } + ws_skip(); + if (CROW_UNLIKELY(*data == ']')) + { + data++; + return ret; + } + + while (1) + { + auto v = decode_value(depth + 1); + if (CROW_UNLIKELY(!v)) + { + ret.set_error(); + break; + } + ws_skip(); + ret.emplace_back(std::move(v)); + if (*data == ']') + { + data++; + break; + } + if (CROW_UNLIKELY(!consume(','))) + { + ret.set_error(); + break; + } + ws_skip(); + } + return ret; + } + + rvalue decode_number() + { + char* start = data; + + enum NumberParsingState + { + Minus, + AfterMinus, + ZeroFirst, + Digits, + DigitsAfterPoints, + E, + DigitsAfterE, + Invalid, + } state{Minus}; + while (CROW_LIKELY(state != Invalid)) + { + switch (*data) + { + case '0': + state = static_cast("\2\2\7\3\4\6\6"[state]); + /*if (state == NumberParsingState::Minus || state == NumberParsingState::AfterMinus) + { + state = NumberParsingState::ZeroFirst; + } + else if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterE || + state == NumberParsingState::DigitsAfterPoints) + { + // ok; pass + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state = static_cast("\3\3\7\3\4\6\6"[state]); + while (*(data + 1) >= '0' && *(data + 1) <= '9') + data++; + /*if (state == NumberParsingState::Minus || state == NumberParsingState::AfterMinus) + { + state = NumberParsingState::Digits; + } + else if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterE || + state == NumberParsingState::DigitsAfterPoints) + { + // ok; pass + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '.': + state = static_cast("\7\7\4\4\7\7\7"[state]); + /* + if (state == NumberParsingState::Digits || state == NumberParsingState::ZeroFirst) + { + state = NumberParsingState::DigitsAfterPoints; + } + else + return {}; + */ + break; + case '-': + state = static_cast("\1\7\7\7\7\6\7"[state]); + /*if (state == NumberParsingState::Minus) + { + state = NumberParsingState::AfterMinus; + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '+': + state = static_cast("\7\7\7\7\7\6\7"[state]); + /*if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case 'e': + case 'E': + state = static_cast("\7\7\7\5\5\7\7"[state]); + /*if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterPoints) + { + state = NumberParsingState::E; + } + else + return {};*/ + break; + default: + if (CROW_LIKELY(state == NumberParsingState::ZeroFirst || + state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterPoints || + state == NumberParsingState::DigitsAfterE)) + return {type::Number, start, data}; + else + return {}; + } + data++; + } + + return {}; + } + + + rvalue decode_value(unsigned depth) + { + switch (*data) + { + case '[': + return decode_list(depth + 1); + case '{': + return decode_object(depth + 1); + case '"': + return decode_string(); + case 't': + if ( //e-data >= 4 && + data[1] == 'r' && + data[2] == 'u' && + data[3] == 'e') + { + data += 4; + return {type::True}; + } + else + return {}; + case 'f': + if ( //e-data >= 5 && + data[1] == 'a' && + data[2] == 'l' && + data[3] == 's' && + data[4] == 'e') + { + data += 5; + return {type::False}; + } + else + return {}; + case 'n': + if ( //e-data >= 4 && + data[1] == 'u' && + data[2] == 'l' && + data[3] == 'l') + { + data += 4; + return {type::Null}; + } + else + return {}; + //case '1': case '2': case '3': + //case '4': case '5': case '6': + //case '7': case '8': case '9': + //case '0': case '-': + default: + return decode_number(); + } + return {}; + } + + rvalue decode_object(unsigned depth) + { + rvalue ret(type::Object); + if (CROW_UNLIKELY(!consume('{')) || CROW_UNLIKELY(depth > max_depth)) + { + ret.set_error(); + return ret; + } + + ws_skip(); + + if (CROW_UNLIKELY(*data == '}')) + { + data++; + return ret; + } + + while (1) + { + auto t = decode_string(); + if (CROW_UNLIKELY(!t)) + { + ret.set_error(); + break; + } + + ws_skip(); + if (CROW_UNLIKELY(!consume(':'))) + { + ret.set_error(); + break; + } + + // TODO(ipkn) caching key to speed up (flyweight?) + // I have no idea how flyweight could apply here, but maybe some speedup can happen if we stopped checking type since decode_string returns a string anyway + auto key = t.s(); + + ws_skip(); + auto v = decode_value(depth + 1); + if (CROW_UNLIKELY(!v)) + { + ret.set_error(); + break; + } + ws_skip(); + + v.key_ = std::move(key); + ret.emplace_back(std::move(v)); + if (CROW_UNLIKELY(*data == '}')) + { + data++; + break; + } + if (CROW_UNLIKELY(!consume(','))) + { + ret.set_error(); + break; + } + ws_skip(); + } + return ret; + } + + rvalue parse() + { + ws_skip(); + auto ret = decode_value(0); // or decode object? + ws_skip(); + if (ret && *data != '\0') + ret.set_error(); + return ret; + } + + char* data; + }; + return Parser(data, size).parse(); + } + inline rvalue load(const char* data, size_t size) + { + char* s = new char[size + 1]; + memcpy(s, data, size); + s[size] = 0; + auto ret = load_nocopy_internal(s, size); + if (ret) + ret.key_.force(s, size); + else + delete[] s; + return ret; + } + + inline rvalue load(const char* data) + { + return load(data, strlen(data)); + } + + inline rvalue load(const std::string& str) + { + return load(str.data(), str.size()); + } + + struct wvalue_reader; + + /// JSON write value. + + /// + /// Value can mean any json value, including a JSON object.
+ /// Write means this class is used to primarily assemble JSON objects using keys and values and export those into a string. + class wvalue : public returnable + { + friend class crow::mustache::template_t; + friend struct wvalue_reader; + + public: + using object = +#ifdef CROW_JSON_USE_MAP + std::map; +#else + std::unordered_map; +#endif + + using list = std::vector; + + type t() const { return t_; } + + /// Create an empty json value (outputs "{}" instead of a "null" string) + static crow::json::wvalue empty_object() { return crow::json::wvalue::object(); } + + private: + type t_{type::Null}; ///< The type of the value. + num_type nt{num_type::Null}; ///< The specific type of the number if \ref t_ is a number. + union number + { + double d; + int64_t si; + uint64_t ui; + + public: + constexpr number() noexcept: + ui() {} /* default constructor initializes unsigned integer. */ + constexpr number(std::uint64_t value) noexcept: + ui(value) {} + constexpr number(std::int64_t value) noexcept: + si(value) {} + explicit constexpr number(double value) noexcept: + d(value) {} + explicit constexpr number(float value) noexcept: + d(value) {} + } num; ///< Value if type is a number. + std::string s; ///< Value if type is a string. + std::unique_ptr l; ///< Value if type is a list. + std::unique_ptr o; ///< Value if type is a JSON object. + std::function f; ///< Value if type is a function (C++ lambda) + + public: + wvalue(): + returnable("application/json") {} + + wvalue(std::nullptr_t): + returnable("application/json"), t_(type::Null) {} + + wvalue(bool value): + returnable("application/json"), t_(value ? type::True : type::False) {} + + wvalue(std::uint8_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Unsigned_integer), num(static_cast(value)) {} + wvalue(std::uint16_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Unsigned_integer), num(static_cast(value)) {} + wvalue(std::uint32_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Unsigned_integer), num(static_cast(value)) {} + wvalue(std::uint64_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Unsigned_integer), num(static_cast(value)) {} + + wvalue(std::int8_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Signed_integer), num(static_cast(value)) {} + wvalue(std::int16_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Signed_integer), num(static_cast(value)) {} + wvalue(std::int32_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Signed_integer), num(static_cast(value)) {} + wvalue(std::int64_t value): + returnable("application/json"), t_(type::Number), nt(num_type::Signed_integer), num(static_cast(value)) {} + + wvalue(float value): + returnable("application/json"), t_(type::Number), nt(num_type::Floating_point), num(static_cast(value)) {} + wvalue(double value): + returnable("application/json"), t_(type::Number), nt(num_type::Double_precision_floating_point), num(static_cast(value)) {} + + wvalue(char const* value): + returnable("application/json"), t_(type::String), s(value) {} + + wvalue(std::string const& value): + returnable("application/json"), t_(type::String), s(value) {} + wvalue(std::string&& value): + returnable("application/json"), t_(type::String), s(std::move(value)) {} + + wvalue(std::initializer_list> initializer_list): + returnable("application/json"), t_(type::Object), o(new object(initializer_list)) {} + + wvalue(object const& value): + returnable("application/json"), t_(type::Object), o(new object(value)) {} + wvalue(object&& value): + returnable("application/json"), t_(type::Object), o(new object(std::move(value))) {} + + wvalue(const list& r): + returnable("application/json") + { + t_ = type::List; + l = std::unique_ptr(new list{}); + l->reserve(r.size()); + for (auto it = r.begin(); it != r.end(); ++it) + l->emplace_back(*it); + } + wvalue(list& r): + returnable("application/json") + { + t_ = type::List; + l = std::unique_ptr(new list{}); + l->reserve(r.size()); + for (auto it = r.begin(); it != r.end(); ++it) + l->emplace_back(*it); + } + + /// Create a write value from a read value (useful for editing JSON strings). + wvalue(const rvalue& r): + returnable("application/json") + { + t_ = r.t(); + switch (r.t()) + { + case type::Null: + case type::False: + case type::True: + case type::Function: + return; + case type::Number: + nt = r.nt(); + if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point) + num.d = r.d(); + else if (nt == num_type::Signed_integer) + num.si = r.i(); + else + num.ui = r.u(); + return; + case type::String: + s = r.s(); + return; + case type::List: + l = std::unique_ptr(new list{}); + l->reserve(r.size()); + for (auto it = r.begin(); it != r.end(); ++it) + l->emplace_back(*it); + return; + case type::Object: + o = std::unique_ptr(new object{}); + for (auto it = r.begin(); it != r.end(); ++it) + o->emplace(it->key(), *it); + return; + } + } + + wvalue(const wvalue& r): + returnable("application/json") + { + t_ = r.t(); + switch (r.t()) + { + case type::Null: + case type::False: + case type::True: + return; + case type::Number: + nt = r.nt; + if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point) + num.d = r.num.d; + else if (nt == num_type::Signed_integer) + num.si = r.num.si; + else + num.ui = r.num.ui; + return; + case type::String: + s = r.s; + return; + case type::List: + l = std::unique_ptr(new list{}); + l->reserve(r.size()); + for (auto it = r.l->begin(); it != r.l->end(); ++it) + l->emplace_back(*it); + return; + case type::Object: + o = std::unique_ptr(new object{}); + o->insert(r.o->begin(), r.o->end()); + return; + case type::Function: + f = r.f; + } + } + + wvalue(wvalue&& r): + returnable("application/json") + { + *this = std::move(r); + } + + wvalue& operator=(wvalue&& r) + { + t_ = r.t_; + nt = r.nt; + num = r.num; + s = std::move(r.s); + l = std::move(r.l); + o = std::move(r.o); + return *this; + } + + /// Used for compatibility, same as \ref reset() + void clear() + { + reset(); + } + + void reset() + { + t_ = type::Null; + l.reset(); + o.reset(); + } + + wvalue& operator=(std::nullptr_t) + { + reset(); + return *this; + } + wvalue& operator=(bool value) + { + reset(); + if (value) + t_ = type::True; + else + t_ = type::False; + return *this; + } + + wvalue& operator=(float value) + { + reset(); + t_ = type::Number; + num.d = value; + nt = num_type::Floating_point; + return *this; + } + + wvalue& operator=(double value) + { + reset(); + t_ = type::Number; + num.d = value; + nt = num_type::Double_precision_floating_point; + return *this; + } + + wvalue& operator=(unsigned short value) + { + reset(); + t_ = type::Number; + num.ui = value; + nt = num_type::Unsigned_integer; + return *this; + } + + wvalue& operator=(short value) + { + reset(); + t_ = type::Number; + num.si = value; + nt = num_type::Signed_integer; + return *this; + } + + wvalue& operator=(long long value) + { + reset(); + t_ = type::Number; + num.si = value; + nt = num_type::Signed_integer; + return *this; + } + + wvalue& operator=(long value) + { + reset(); + t_ = type::Number; + num.si = value; + nt = num_type::Signed_integer; + return *this; + } + + wvalue& operator=(int value) + { + reset(); + t_ = type::Number; + num.si = value; + nt = num_type::Signed_integer; + return *this; + } + + wvalue& operator=(unsigned long long value) + { + reset(); + t_ = type::Number; + num.ui = value; + nt = num_type::Unsigned_integer; + return *this; + } + + wvalue& operator=(unsigned long value) + { + reset(); + t_ = type::Number; + num.ui = value; + nt = num_type::Unsigned_integer; + return *this; + } + + wvalue& operator=(unsigned int value) + { + reset(); + t_ = type::Number; + num.ui = value; + nt = num_type::Unsigned_integer; + return *this; + } + + wvalue& operator=(const char* str) + { + reset(); + t_ = type::String; + s = str; + return *this; + } + + wvalue& operator=(const std::string& str) + { + reset(); + t_ = type::String; + s = str; + return *this; + } + + wvalue& operator=(list&& v) + { + if (t_ != type::List) + reset(); + t_ = type::List; + if (!l) + l = std::unique_ptr(new list{}); + l->clear(); + l->resize(v.size()); + size_t idx = 0; + for (auto& x : v) + { + (*l)[idx++] = std::move(x); + } + return *this; + } + + template + wvalue& operator=(const std::vector& v) + { + if (t_ != type::List) + reset(); + t_ = type::List; + if (!l) + l = std::unique_ptr(new list{}); + l->clear(); + l->resize(v.size()); + size_t idx = 0; + for (auto& x : v) + { + (*l)[idx++] = x; + } + return *this; + } + + wvalue& operator=(std::initializer_list> initializer_list) + { + if (t_ != type::Object) + { + reset(); + t_ = type::Object; + o = std::unique_ptr(new object(initializer_list)); + } + else + { +#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(_LIBCPP_VERSION) + o = std::unique_ptr(new object(initializer_list)); +#else + (*o) = initializer_list; +#endif + } + return *this; + } + + wvalue& operator=(object const& value) + { + if (t_ != type::Object) + { + reset(); + t_ = type::Object; + o = std::unique_ptr(new object(value)); + } + else + { +#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(_LIBCPP_VERSION) + o = std::unique_ptr(new object(value)); +#else + (*o) = value; +#endif + } + return *this; + } + + wvalue& operator=(object&& value) + { + if (t_ != type::Object) + { + reset(); + t_ = type::Object; + o = std::unique_ptr(new object(std::move(value))); + } + else + { + (*o) = std::move(value); + } + return *this; + } + + wvalue& operator=(std::function&& func) + { + reset(); + t_ = type::Function; + f = std::move(func); + return *this; + } + + wvalue& operator[](unsigned index) + { + if (t_ != type::List) + reset(); + t_ = type::List; + if (!l) + l = std::unique_ptr(new list{}); + if (l->size() < index + 1) + l->resize(index + 1); + return (*l)[index]; + } + + const wvalue& operator[](unsigned index) const + { + return const_cast(this)->operator[](index); + } + + int count(const std::string& str) const + { + if (t_ != type::Object) + return 0; + if (!o) + return 0; + return o->count(str); + } + + wvalue& operator[](const std::string& str) + { + if (t_ != type::Object) + reset(); + t_ = type::Object; + if (!o) + o = std::unique_ptr(new object{}); + return (*o)[str]; + } + + const wvalue& operator[](const std::string& str) const + { + return const_cast(this)->operator[](str); + } + + std::vector keys() const + { + if (t_ != type::Object) + return {}; + std::vector result; + for (auto& kv : *o) + { + result.push_back(kv.first); + } + return result; + } + + std::string execute(std::string txt = "") const //Not using reference because it cannot be used with a default rvalue + { + if (t_ != type::Function) + return ""; + return f(txt); + } + + /// If the wvalue is a list, it returns the length of the list, otherwise it returns 1. + std::size_t size() const + { + if (t_ != type::List) + return 1; + return l->size(); + } + + /// Returns an estimated size of the value in bytes. + size_t estimate_length() const + { + switch (t_) + { + case type::Null: return 4; + case type::False: return 5; + case type::True: return 4; + case type::Number: return 30; + case type::String: return 2 + s.size() + s.size() / 2; + case type::List: + { + size_t sum{}; + if (l) + { + for (auto& x : *l) + { + sum += 1; + sum += x.estimate_length(); + } + } + return sum + 2; + } + case type::Object: + { + size_t sum{}; + if (o) + { + for (auto& kv : *o) + { + sum += 2; + sum += 2 + kv.first.size() + kv.first.size() / 2; + sum += kv.second.estimate_length(); + } + } + return sum + 2; + } + case type::Function: + return 0; + } + return 1; + } + + private: + inline void dump_string(const std::string& str, std::string& out) const + { + out.push_back('"'); + escape(str, out); + out.push_back('"'); + } + + inline void dump_indentation_part(std::string& out, const int indent, const char separator, const int indent_level) const + { + out.push_back('\n'); + out.append(indent_level * indent, separator); + } + + + inline void dump_internal(const wvalue& v, std::string& out, const int indent, const char separator, const int indent_level = 0) const + { + switch (v.t_) + { + case type::Null: out += "null"; break; + case type::False: out += "false"; break; + case type::True: out += "true"; break; + case type::Number: + { + if (v.nt == num_type::Floating_point || v.nt == num_type::Double_precision_floating_point) + { + if (isnan(v.num.d) || isinf(v.num.d)) + { + out += "null"; + CROW_LOG_WARNING << "Invalid JSON value detected (" << v.num.d << "), value set to null"; + break; + } + enum + { + start, + decp, // Decimal point + zero + } f_state; + char outbuf[128]; + if (v.nt == num_type::Double_precision_floating_point) + { +#ifdef _MSC_VER + sprintf_s(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d); +#else + snprintf(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d); +#endif + } + else + { +#ifdef _MSC_VER + sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d); +#else + snprintf(outbuf, sizeof(outbuf), "%f", v.num.d); +#endif + } + char *p = &outbuf[0], *o = nullptr; // o is the position of the first trailing 0 + f_state = start; + while (*p != '\0') + { + //std::cout << *p << std::endl; + char ch = *p; + switch (f_state) + { + case start: // Loop and lookahead until a decimal point is found + if (ch == '.') + { + char fch = *(p + 1); + // if the first character is 0, leave it be (this is so that "1.00000" becomes "1.0" and not "1.") + if (fch != '\0' && fch == '0') p++; + f_state = decp; + } + p++; + break; + case decp: // Loop until a 0 is found, if found, record its position + if (ch == '0') + { + f_state = zero; + o = p; + } + p++; + break; + case zero: // if a non 0 is found (e.g. 1.00004) remove the earlier recorded 0 position and look for more trailing 0s + if (ch != '0') + { + o = nullptr; + f_state = decp; + } + p++; + break; + } + } + if (o != nullptr) // if any trailing 0s are found, terminate the string where they begin + *o = '\0'; + out += outbuf; + } + else if (v.nt == num_type::Signed_integer) + { + out += std::to_string(v.num.si); + } + else + { + out += std::to_string(v.num.ui); + } + } + break; + case type::String: dump_string(v.s, out); break; + case type::List: + { + out.push_back('['); + + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level + 1); + } + + if (v.l) + { + bool first = true; + for (auto& x : *v.l) + { + if (!first) + { + out.push_back(','); + + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level + 1); + } + } + first = false; + dump_internal(x, out, indent, separator, indent_level + 1); + } + } + + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level); + } + + out.push_back(']'); + } + break; + case type::Object: + { + out.push_back('{'); + + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level + 1); + } + + if (v.o) + { + bool first = true; + for (auto& kv : *v.o) + { + if (!first) + { + out.push_back(','); + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level + 1); + } + } + first = false; + dump_string(kv.first, out); + out.push_back(':'); + + if (indent >= 0) + { + out.push_back(' '); + } + + dump_internal(kv.second, out, indent, separator, indent_level + 1); + } + } + + if (indent >= 0) + { + dump_indentation_part(out, indent, separator, indent_level); + } + + out.push_back('}'); + } + break; + + case type::Function: + out += "custom function"; + break; + } + } + + public: + std::string dump(const int indent, const char separator = ' ') const + { + std::string ret; + ret.reserve(estimate_length()); + dump_internal(*this, ret, indent, separator); + return ret; + } + + std::string dump() const + { + static constexpr int DontIndent = -1; + + return dump(DontIndent); + } + }; + + // Used for accessing the internals of a wvalue + struct wvalue_reader + { + int64_t get(int64_t fallback) + { + if (ref.t() != type::Number || ref.nt == num_type::Floating_point || + ref.nt == num_type::Double_precision_floating_point) + return fallback; + return ref.num.si; + } + + double get(double fallback) + { + if (ref.t() != type::Number || ref.nt != num_type::Floating_point || + ref.nt == num_type::Double_precision_floating_point) + return fallback; + return ref.num.d; + } + + bool get(bool fallback) + { + if (ref.t() == type::True) return true; + if (ref.t() == type::False) return false; + return fallback; + } + + std::string get(const std::string& fallback) + { + if (ref.t() != type::String) return fallback; + return ref.s; + } + + const wvalue& ref; + }; + + //std::vector dump_ref(wvalue& v) + //{ + //} + } // namespace json +} // namespace crow + +#include +#include +#include +#include +#include +// S_ISREG is not defined for windows +// This defines it like suggested in https://stackoverflow.com/a/62371749 +#if defined(_MSC_VER) +#define _CRT_INTERNAL_NONSTDC_NAMES 1 +#endif +#include +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#endif + + + +namespace crow +{ + template + class Connection; + + class Router; + + /// HTTP response + struct response + { + template + friend class crow::Connection; + + friend class Router; + + int code{200}; ///< The Status code for the response. + std::string body; ///< The actual payload containing the response data. + ci_map headers; ///< HTTP headers. + +#ifdef CROW_ENABLE_COMPRESSION + bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed. +#endif + bool skip_body = false; ///< Whether this is a response to a HEAD request. + bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header. + + /// Set the value of an existing header in the response. + void set_header(std::string key, std::string value) + { + headers.erase(key); + headers.emplace(std::move(key), std::move(value)); + } + + /// Add a new header to the response. + void add_header(std::string key, std::string value) + { + headers.emplace(std::move(key), std::move(value)); + } + + const std::string& get_header_value(const std::string& key) + { + return crow::get_header_value(headers, key); + } + + // naive validation of a mime-type string + static bool validate_mime_type(const std::string& candidate) noexcept + { + // Here we simply check that the candidate type starts with + // a valid parent type, and has at least one character afterwards. + std::array valid_parent_types = { + "application/", "audio/", "font/", "example/", + "image/", "message/", "model/", "multipart/", + "text/", "video/"}; + for (const std::string& parent : valid_parent_types) + { + // ensure the candidate is *longer* than the parent, + // to avoid unnecessary string comparison and to + // reject zero-length subtypes. + if (candidate.size() <= parent.size()) + { + continue; + } + // strncmp is used rather than substr to avoid allocation, + // but a string_view approach would be better if Crow + // migrates to C++17. + if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0) + { + return true; + } + } + return false; + } + + // Find the mime type from the content type either by lookup, + // or by the content type itself, if it is a valid a mime type. + // Defaults to text/plain. + static std::string get_mime_type(const std::string& contentType) + { + const auto mimeTypeIterator = mime_types.find(contentType); + if (mimeTypeIterator != mime_types.end()) + { + return mimeTypeIterator->second; + } + else if (validate_mime_type(contentType)) + { + return contentType; + } + else + { + CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain."; + return "text/plain"; + } + } + + + // clang-format off + response() {} + explicit response(int code) : code(code) {} + response(std::string body) : body(std::move(body)) {} + response(int code, std::string body) : code(code), body(std::move(body)) {} + // clang-format on + response(returnable&& value) + { + body = value.dump(); + set_header("Content-Type", value.content_type); + } + response(returnable& value) + { + body = value.dump(); + set_header("Content-Type", value.content_type); + } + response(int code, returnable& value): + code(code) + { + body = value.dump(); + set_header("Content-Type", value.content_type); + } + response(int code, returnable&& value): + code(code), body(value.dump()) + { + set_header("Content-Type", std::move(value.content_type)); + } + + response(response&& r) + { + *this = std::move(r); + } + + response(std::string contentType, std::string body): + body(std::move(body)) + { + set_header("Content-Type", get_mime_type(contentType)); + } + + response(int code, std::string contentType, std::string body): + code(code), body(std::move(body)) + { + set_header("Content-Type", get_mime_type(contentType)); + } + + response& operator=(const response& r) = delete; + + response& operator=(response&& r) noexcept + { + body = std::move(r.body); + code = r.code; + headers = std::move(r.headers); + completed_ = r.completed_; + file_info = std::move(r.file_info); + return *this; + } + + /// Check if the response has completed (whether response.end() has been called) + bool is_completed() const noexcept + { + return completed_; + } + + void clear() + { + body.clear(); + code = 200; + headers.clear(); + completed_ = false; + file_info = static_file_info{}; + } + + /// Return a "Temporary Redirect" response. + + /// + /// Location can either be a route or a full URL. + void redirect(const std::string& location) + { + code = 307; + set_header("Location", location); + } + + /// Return a "Permanent Redirect" response. + + /// + /// Location can either be a route or a full URL. + void redirect_perm(const std::string& location) + { + code = 308; + set_header("Location", location); + } + + /// Return a "Found (Moved Temporarily)" response. + + /// + /// Location can either be a route or a full URL. + void moved(const std::string& location) + { + code = 302; + set_header("Location", location); + } + + /// Return a "Moved Permanently" response. + + /// + /// Location can either be a route or a full URL. + void moved_perm(const std::string& location) + { + code = 301; + set_header("Location", location); + } + + void write(const std::string& body_part) + { + body += body_part; + } + + /// Set the response completion flag and call the handler (to send the response). + void end() + { + if (!completed_) + { + completed_ = true; + if (skip_body) + { + set_header("Content-Length", std::to_string(body.size())); + body = ""; + manual_length_header = true; + } + if (complete_request_handler_) + { + complete_request_handler_(); + manual_length_header = false; + skip_body = false; + } + } + } + + /// Same as end() except it adds a body part right before ending. + void end(const std::string& body_part) + { + body += body_part; + end(); + } + + /// Check if the connection is still alive (usually by checking the socket status). + bool is_alive() + { + return is_alive_helper_ && is_alive_helper_(); + } + + /// Check whether the response has a static file defined. + bool is_static_type() + { + return file_info.path.size(); + } + + /// This constains metadata (coming from the `stat` command) related to any static files associated with this response. + + /// + /// Either a static file or a string body can be returned as 1 response. + struct static_file_info + { + std::string path = ""; + struct stat statbuf; + int statResult; + }; + + /// Return a static file as the response body + void set_static_file_info(std::string path) + { + utility::sanitize_filename(path); + set_static_file_info_unsafe(path); + } + + /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead) + void set_static_file_info_unsafe(std::string path) + { + file_info.path = path; + file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf); +#ifdef CROW_ENABLE_COMPRESSION + compressed = false; +#endif + if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode)) + { + std::size_t last_dot = path.find_last_of("."); + std::string extension = path.substr(last_dot + 1); + code = 200; + this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size)); + + if (!extension.empty()) + { + this->add_header("Content-Type", get_mime_type(extension)); + } + } + else + { + code = 404; + file_info.path.clear(); + } + } + + private: + bool completed_{}; + std::function complete_request_handler_; + std::function is_alive_helper_; + static_file_info file_info; + }; +} // namespace crow + + +namespace crow +{ + + struct UTF8 + { + struct context + {}; + + void before_handle(request& /*req*/, response& /*res*/, context& /*ctx*/) + {} + + void after_handle(request& /*req*/, response& res, context& /*ctx*/) + { + if (get_header_value(res.headers, "Content-Type").empty()) + { + res.set_header("Content-Type", "text/plain; charset=utf-8"); + } + } + }; + +} // namespace crow + +#include +#include + +namespace crow +{ + // Any middleware requires following 3 members: + + // struct context; + // storing data for the middleware; can be read from another middleware or handlers + + // before_handle + // called before handling the request. + // if res.end() is called, the operation is halted. + // (still call after_handle of this middleware) + // 2 signatures: + // void before_handle(request& req, response& res, context& ctx) + // if you only need to access this middlewares context. + // template + // void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + // you can access another middlewares' context by calling `all_ctx.template get()' + // ctx == all_ctx.template get() + + // after_handle + // called after handling the request. + // void after_handle(request& req, response& res, context& ctx) + // template + // void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + + struct CookieParser + { + // Cookie stores key, value and attributes + struct Cookie + { + enum class SameSitePolicy + { + Strict, + Lax, + None + }; + + template + Cookie(const std::string& key, U&& value): + Cookie() + { + key_ = key; + value_ = std::forward(value); + } + + Cookie(const std::string& key): + Cookie(key, "") {} + + // format cookie to HTTP header format + std::string dump() const + { + const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"; + + std::stringstream ss; + ss << key_ << '='; + ss << (value_.empty() ? "\"\"" : value_); + dumpString(ss, !domain_.empty(), "Domain=", domain_); + dumpString(ss, !path_.empty(), "Path=", path_); + dumpString(ss, secure_, "Secure"); + dumpString(ss, httponly_, "HttpOnly"); + if (expires_at_) + { + ss << DIVIDER << "Expires=" + << std::put_time(expires_at_.get(), HTTP_DATE_FORMAT); + } + if (max_age_) + { + ss << DIVIDER << "Max-Age=" << *max_age_; + } + if (same_site_) + { + ss << DIVIDER << "SameSite="; + switch (*same_site_) + { + case SameSitePolicy::Strict: + ss << "Strict"; + break; + case SameSitePolicy::Lax: + ss << "Lax"; + break; + case SameSitePolicy::None: + ss << "None"; + break; + } + } + return ss.str(); + } + + const std::string& name() + { + return key_; + } + + template + Cookie& value(U&& value) + { + value_ = std::forward(value); + return *this; + } + + // Expires attribute + Cookie& expires(const std::tm& time) + { + expires_at_ = std::unique_ptr(new std::tm(time)); + return *this; + } + + // Max-Age attribute + Cookie& max_age(long long seconds) + { + max_age_ = std::unique_ptr(new long long(seconds)); + return *this; + } + + // Domain attribute + Cookie& domain(const std::string& name) + { + domain_ = name; + return *this; + } + + // Path attribute + Cookie& path(const std::string& path) + { + path_ = path; + return *this; + } + + // Secured attribute + Cookie& secure() + { + secure_ = true; + return *this; + } + + // HttpOnly attribute + Cookie& httponly() + { + httponly_ = true; + return *this; + } + + // SameSite attribute + Cookie& same_site(SameSitePolicy ssp) + { + same_site_ = std::unique_ptr(new SameSitePolicy(ssp)); + return *this; + } + + Cookie(const Cookie& c): + key_(c.key_), + value_(c.value_), + domain_(c.domain_), + path_(c.path_), + secure_(c.secure_), + httponly_(c.httponly_) + { + if (c.max_age_) + max_age_ = std::unique_ptr(new long long(*c.max_age_)); + + if (c.expires_at_) + expires_at_ = std::unique_ptr(new std::tm(*c.expires_at_)); + + if (c.same_site_) + same_site_ = std::unique_ptr(new SameSitePolicy(*c.same_site_)); + } + + private: + Cookie() = default; + + static void dumpString(std::stringstream& ss, bool cond, const char* prefix, + const std::string& value = "") + { + if (cond) + { + ss << DIVIDER << prefix << value; + } + } + + private: + std::string key_; + std::string value_; + std::unique_ptr max_age_{}; + std::string domain_ = ""; + std::string path_ = ""; + bool secure_ = false; + bool httponly_ = false; + std::unique_ptr expires_at_{}; + std::unique_ptr same_site_{}; + + static constexpr const char* DIVIDER = "; "; + }; + + + struct context + { + std::unordered_map jar; + + std::string get_cookie(const std::string& key) const + { + auto cookie = jar.find(key); + if (cookie != jar.end()) + return cookie->second; + return {}; + } + + template + Cookie& set_cookie(const std::string& key, U&& value) + { + cookies_to_add.emplace_back(key, std::forward(value)); + return cookies_to_add.back(); + } + + Cookie& set_cookie(Cookie cookie) + { + cookies_to_add.push_back(std::move(cookie)); + return cookies_to_add.back(); + } + + private: + friend struct CookieParser; + std::vector cookies_to_add; + }; + + void before_handle(request& req, response& res, context& ctx) + { + // TODO(dranikpg): remove copies, use string_view with c++17 + int count = req.headers.count("Cookie"); + if (!count) + return; + if (count > 1) + { + res.code = 400; + res.end(); + return; + } + std::string cookies = req.get_header_value("Cookie"); + size_t pos = 0; + while (pos < cookies.size()) + { + size_t pos_equal = cookies.find('=', pos); + if (pos_equal == cookies.npos) + break; + std::string name = cookies.substr(pos, pos_equal - pos); + name = utility::trim(name); + pos = pos_equal + 1; + if (pos == cookies.size()) + break; + + size_t pos_semicolon = cookies.find(';', pos); + std::string value = cookies.substr(pos, pos_semicolon - pos); + + value = utility::trim(value); + if (value[0] == '"' && value[value.size() - 1] == '"') + { + value = value.substr(1, value.size() - 2); + } + + ctx.jar.emplace(std::move(name), std::move(value)); + + pos = pos_semicolon; + if (pos == cookies.npos) + break; + pos++; + } + } + + void after_handle(request& /*req*/, response& res, context& ctx) + { + for (const auto& cookie : ctx.cookies_to_add) + { + res.add_header("Set-Cookie", cookie.dump()); + } + } + }; + + /* + App app; + A B C + A::context + int aa; + + ctx1 : public A::context + ctx2 : public ctx1, public B::context + ctx3 : public ctx2, public C::context + + C depends on A + + C::handle + context.aaa + + App::context : private CookieParser::context, ... + { + jar + + } + + SimpleApp + */ +} // namespace crow + + + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef CROW_CAN_USE_CPP17 +#include +#endif + +namespace +{ + // convert all integer values to int64_t + template + using wrap_integral_t = typename std::conditional< + std::is_integral::value && !std::is_same::value + // except for uint64_t because that could lead to overflow on conversion + && !std::is_same::value, + int64_t, T>::type; + + // convert char[]/char* to std::string + template + using wrap_char_t = typename std::conditional< + std::is_same::type, char*>::value, + std::string, T>::type; + + // Upgrade to correct type for multi_variant use + template + using wrap_mv_t = wrap_char_t>; +} // namespace + +namespace crow +{ + namespace session + { + +#ifdef CROW_CAN_USE_CPP17 + using multi_value_types = black_magic::S; + + /// A multi_value is a safe variant wrapper with json conversion support + struct multi_value + { + json::wvalue json() const + { + // clang-format off + return std::visit([](auto arg) { + return json::wvalue(arg); + }, v_); + // clang-format on + } + + static multi_value from_json(const json::rvalue&); + + std::string string() const + { + // clang-format off + return std::visit([](auto arg) { + if constexpr (std::is_same_v) + return arg; + else + return std::to_string(arg); + }, v_); + // clang-format on + } + + template> + RT get(const T& fallback) + { + if (const RT* val = std::get_if(&v_)) return *val; + return fallback; + } + + template> + void set(T val) + { + v_ = RT(std::move(val)); + } + + typename multi_value_types::rebind v_; + }; + + inline multi_value multi_value::from_json(const json::rvalue& rv) + { + using namespace json; + switch (rv.t()) + { + case type::Number: + { + if (rv.nt() == num_type::Floating_point || rv.nt() == num_type::Double_precision_floating_point) + return multi_value{rv.d()}; + else if (rv.nt() == num_type::Unsigned_integer) + return multi_value{int64_t(rv.u())}; + else + return multi_value{rv.i()}; + } + case type::False: return multi_value{false}; + case type::True: return multi_value{true}; + case type::String: return multi_value{std::string(rv)}; + default: return multi_value{false}; + } + } +#else + // Fallback for C++11/14 that uses a raw json::wvalue internally. + // This implementation consumes significantly more memory + // than the variant-based version + struct multi_value + { + json::wvalue json() const { return v_; } + + static multi_value from_json(const json::rvalue&); + + std::string string() const { return v_.dump(); } + + template> + RT get(const T& fallback) + { + return json::wvalue_reader{v_}.get((const RT&)(fallback)); + } + + template> + void set(T val) + { + v_ = RT(std::move(val)); + } + + json::wvalue v_; + }; + + inline multi_value multi_value::from_json(const json::rvalue& rv) + { + return {rv}; + } +#endif + + /// Expiration tracker keeps track of soonest-to-expire keys + struct ExpirationTracker + { + using DataPair = std::pair; + + /// Add key with time to tracker. + /// If the key is already present, it will be updated + void add(std::string key, uint64_t time) + { + auto it = times_.find(key); + if (it != times_.end()) remove(key); + times_[key] = time; + queue_.insert({time, std::move(key)}); + } + + void remove(const std::string& key) + { + auto it = times_.find(key); + if (it != times_.end()) + { + queue_.erase({it->second, key}); + times_.erase(it); + } + } + + /// Get expiration time of soonest-to-expire entry + uint64_t peek_first() const + { + if (queue_.empty()) return std::numeric_limits::max(); + return queue_.begin()->first; + } + + std::string pop_first() + { + auto it = times_.find(queue_.begin()->second); + auto key = it->first; + times_.erase(it); + queue_.erase(queue_.begin()); + return key; + } + + using iterator = typename std::set::const_iterator; + + iterator begin() const { return queue_.cbegin(); } + + iterator end() const { return queue_.cend(); } + + private: + std::set queue_; + std::unordered_map times_; + }; + + /// CachedSessions are shared across requests + struct CachedSession + { + std::string session_id; + std::string requested_session_id; // session hasn't been created yet, but a key was requested + + std::unordered_map entries; + std::unordered_set dirty; // values that were changed after last load + + void* store_data; + bool requested_refresh; + + // number of references held - used for correctly destroying the cache. + // No need to be atomic, all SessionMiddleware accesses are synchronized + int referrers; + std::recursive_mutex mutex; + }; + } // namespace session + + // SessionMiddleware allows storing securely and easily small snippets of user information + template + struct SessionMiddleware + { +#ifdef CROW_CAN_USE_CPP17 + using lock = std::scoped_lock; + using rc_lock = std::scoped_lock; +#else + using lock = std::lock_guard; + using rc_lock = std::lock_guard; +#endif + + struct context + { + // Get a mutex for locking this session + std::recursive_mutex& mutex() + { + check_node(); + return node->mutex; + } + + // Check whether this session is already present + bool exists() { return bool(node); } + + // Get a value by key or fallback if it doesn't exist or is of another type + template + auto get(const std::string& key, const F& fallback = F()) + // This trick lets the multi_value deduce the return type from the fallback + // which allows both: + // context.get("key") + // context.get("key", "") -> char[] is transformed into string by multivalue + // to return a string + -> decltype(std::declval().get(std::declval())) + { + if (!node) return fallback; + rc_lock l(node->mutex); + + auto it = node->entries.find(key); + if (it != node->entries.end()) return it->second.get(fallback); + return fallback; + } + + // Set a value by key + template + void set(const std::string& key, T value) + { + check_node(); + rc_lock l(node->mutex); + + node->dirty.insert(key); + node->entries[key].set(std::move(value)); + } + + bool contains(const std::string& key) + { + if (!node) return false; + return node->entries.find(key) != node->entries.end(); + } + + // Atomically mutate a value with a function + template + void apply(const std::string& key, const Func& f) + { + using traits = utility::function_traits; + using arg = typename std::decay>::type; + using retv = typename std::decay::type; + check_node(); + rc_lock l(node->mutex); + node->dirty.insert(key); + node->entries[key].set(f(node->entries[key].get(arg{}))); + } + + // Remove a value from the session + void remove(const std::string& key) + { + if (!node) return; + rc_lock l(node->mutex); + node->dirty.insert(key); + node->entries.erase(key); + } + + // Format value by key as a string + std::string string(const std::string& key) + { + if (!node) return ""; + rc_lock l(node->mutex); + + auto it = node->entries.find(key); + if (it != node->entries.end()) return it->second.string(); + return ""; + } + + // Get a list of keys present in session + std::vector keys() + { + if (!node) return {}; + rc_lock l(node->mutex); + + std::vector out; + for (const auto& p : node->entries) + out.push_back(p.first); + return out; + } + + // Delay expiration by issuing another cookie with an updated expiration time + // and notifying the store + void refresh_expiration() + { + if (!node) return; + node->requested_refresh = true; + } + + private: + friend struct SessionMiddleware; + + void check_node() + { + if (!node) node = std::make_shared(); + } + + std::shared_ptr node; + }; + + template + SessionMiddleware( + CookieParser::Cookie cookie, + int id_length, + Ts... ts): + id_length_(id_length), + cookie_(cookie), + store_(std::forward(ts)...), mutex_(new std::mutex{}) + {} + + template + SessionMiddleware(Ts... ts): + SessionMiddleware( + CookieParser::Cookie("session").path("/").max_age(/*month*/ 30 * 24 * 60 * 60), + /*id_length */ 20, // around 10^34 possible combinations, but small enough to fit into SSO + std::forward(ts)...) + {} + + template + void before_handle(request& /*req*/, response& /*res*/, context& ctx, AllContext& all_ctx) + { + lock l(*mutex_); + + auto& cookies = all_ctx.template get(); + auto session_id = load_id(cookies); + if (session_id == "") return; + + // search entry in cache + auto it = cache_.find(session_id); + if (it != cache_.end()) + { + it->second->referrers++; + ctx.node = it->second; + return; + } + + // check this is a valid entry before loading + if (!store_.contains(session_id)) return; + + auto node = std::make_shared(); + node->session_id = session_id; + node->referrers = 1; + + try + { + store_.load(*node); + } + catch (...) + { + CROW_LOG_ERROR << "Exception occurred during session load"; + return; + } + + ctx.node = node; + cache_[session_id] = node; + } + + template + void after_handle(request& /*req*/, response& /*res*/, context& ctx, AllContext& all_ctx) + { + lock l(*mutex_); + if (!ctx.node || --ctx.node->referrers > 0) return; + ctx.node->requested_refresh |= ctx.node->session_id == ""; + + // generate new id + if (ctx.node->session_id == "") + { + // check for requested id + ctx.node->session_id = std::move(ctx.node->requested_session_id); + if (ctx.node->session_id == "") + { + ctx.node->session_id = utility::random_alphanum(id_length_); + } + } + else + { + cache_.erase(ctx.node->session_id); + } + + if (ctx.node->requested_refresh) + { + auto& cookies = all_ctx.template get(); + store_id(cookies, ctx.node->session_id); + } + + try + { + store_.save(*ctx.node); + } + catch (...) + { + CROW_LOG_ERROR << "Exception occurred during session save"; + return; + } + } + + private: + std::string next_id() + { + std::string id; + do + { + id = utility::random_alphanum(id_length_); + } while (store_.contains(id)); + return id; + } + + std::string load_id(const CookieParser::context& cookies) + { + return cookies.get_cookie(cookie_.name()); + } + + void store_id(CookieParser::context& cookies, const std::string& session_id) + { + cookie_.value(session_id); + cookies.set_cookie(cookie_); + } + + private: + int id_length_; + + // prototype for cookie + CookieParser::Cookie cookie_; + + Store store_; + + // mutexes are immovable + std::unique_ptr mutex_; + std::unordered_map> cache_; + }; + + /// InMemoryStore stores all entries in memory + struct InMemoryStore + { + // Load a value into the session cache. + // A load is always followed by a save, no loads happen consecutively + void load(session::CachedSession& cn) + { + // load & stores happen sequentially, so moving is safe + cn.entries = std::move(entries[cn.session_id]); + } + + // Persist session data + void save(session::CachedSession& cn) + { + entries[cn.session_id] = std::move(cn.entries); + // cn.dirty is a list of changed keys since the last load + } + + bool contains(const std::string& key) + { + return entries.count(key) > 0; + } + + std::unordered_map> entries; + }; + + // FileStore stores all data as json files in a folder. + // Files are deleted after expiration. Expiration refreshes are automatically picked up. + struct FileStore + { + FileStore(const std::string& folder, uint64_t expiration_seconds = /*month*/ 30 * 24 * 60 * 60): + path_(folder), expiration_seconds_(expiration_seconds) + { + std::ifstream ifs(get_filename(".expirations", false)); + + auto current_ts = chrono_time(); + std::string key; + uint64_t time; + while (ifs >> key >> time) + { + if (current_ts > time) + { + evict(key); + } + else if (contains(key)) + { + expirations_.add(key, time); + } + } + } + + ~FileStore() + { + std::ofstream ofs(get_filename(".expirations", false), std::ios::trunc); + for (const auto& p : expirations_) + ofs << p.second << " " << p.first << "\n"; + } + + // Delete expired entries + // At most 3 to prevent freezes + void handle_expired() + { + int deleted = 0; + auto current_ts = chrono_time(); + while (current_ts > expirations_.peek_first() && deleted < 3) + { + evict(expirations_.pop_first()); + deleted++; + } + } + + void load(session::CachedSession& cn) + { + handle_expired(); + + std::ifstream file(get_filename(cn.session_id)); + + std::stringstream buffer; + buffer << file.rdbuf() << std::endl; + + for (const auto& p : json::load(buffer.str())) + cn.entries[p.key()] = session::multi_value::from_json(p); + } + + void save(session::CachedSession& cn) + { + if (cn.requested_refresh) + expirations_.add(cn.session_id, chrono_time() + expiration_seconds_); + if (cn.dirty.empty()) return; + + std::ofstream file(get_filename(cn.session_id)); + json::wvalue jw; + for (const auto& p : cn.entries) + jw[p.first] = p.second.json(); + file << jw.dump() << std::flush; + } + + std::string get_filename(const std::string& key, bool suffix = true) + { + return utility::join_path(path_, key + (suffix ? ".json" : "")); + } + + bool contains(const std::string& key) + { + std::ifstream file(get_filename(key)); + return file.good(); + } + + void evict(const std::string& key) + { + std::remove(get_filename(key).c_str()); + } + + uint64_t chrono_time() const + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + } + + std::string path_; + uint64_t expiration_seconds_; + session::ExpirationTracker expirations_; + }; + +} // namespace crow + + + +#include +#include +#include +#include + +namespace crow // NOTE: Already documented in "crow/app.h" +{ + + /// Local middleware should extend ILocalMiddleware + struct ILocalMiddleware + { + using call_global = std::false_type; + }; + + namespace detail + { + template + struct check_before_handle_arity_3_const + { + template + struct get + {}; + }; + + template + struct check_before_handle_arity_3 + { + template + struct get + {}; + }; + + template + struct check_after_handle_arity_3_const + { + template + struct get + {}; + }; + + template + struct check_after_handle_arity_3 + { + template + struct get + {}; + }; + + template + struct check_global_call_false + { + template::type = true> + struct get + {}; + }; + + template + struct is_before_handle_arity_3_impl + { + template + static std::true_type f(typename check_before_handle_arity_3_const::template get*); + + template + static std::true_type f(typename check_before_handle_arity_3::template get*); + + template + static std::false_type f(...); + + public: + static const bool value = decltype(f(nullptr))::value; + }; + + template + struct is_after_handle_arity_3_impl + { + template + static std::true_type f(typename check_after_handle_arity_3_const::template get*); + + template + static std::true_type f(typename check_after_handle_arity_3::template get*); + + template + static std::false_type f(...); + + public: + static constexpr bool value = decltype(f(nullptr))::value; + }; + + template + struct is_middleware_global + { + template + static std::false_type f(typename check_global_call_false::template get*); + + template + static std::true_type f(...); + + static const bool value = decltype(f(nullptr))::value; + }; + + template + typename std::enable_if::value>::type + before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.before_handle(req, res, ctx.template get(), ctx); + } + + template + typename std::enable_if::value>::type + before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.before_handle(req, res, ctx.template get()); + } + + template + typename std::enable_if::value>::type + after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.after_handle(req, res, ctx.template get(), ctx); + } + + template + typename std::enable_if::value>::type + after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.after_handle(req, res, ctx.template get()); + } + + + template + typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type + middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx) + { + + using CurrentMW = typename std::tuple_element::type>::type; + + if (!cc.template enabled(N)) + { + return middleware_call_helper(cc, middlewares, req, res, ctx); + } + + using parent_context_t = typename Context::template partial; + before_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + if (res.is_completed()) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + return true; + } + + if (middleware_call_helper(cc, middlewares, req, res, ctx)) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + return true; + } + + return false; + } + + template + typename std::enable_if<(N >= std::tuple_size::type>::value), bool>::type + middleware_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/) + { + return false; + } + + template + typename std::enable_if<(N < 0)>::type + after_handlers_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/) + { + } + + template + typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res) + { + using parent_context_t = typename Context::template partial; + using CurrentMW = typename std::tuple_element::type>::type; + if (cc.template enabled(N)) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + } + } + + template + typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res) + { + using parent_context_t = typename Context::template partial; + using CurrentMW = typename std::tuple_element::type>::type; + if (cc.template enabled(N)) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + } + after_handlers_call_helper(cc, middlewares, ctx, req, res); + } + + // A CallCriteria that accepts only global middleware + struct middleware_call_criteria_only_global + { + template + constexpr bool enabled(int) const + { + return is_middleware_global::value; + } + }; + + template + typename std::enable_if>::value, void>::type + wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args) + { + static_assert(!std::is_same()...))>::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + res = crow::response(f(std::forward(args)...)); + res.end(); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + black_magic::CallHelper>::value, + void>::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(!std::is_same(), std::declval()...))>::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + res = crow::response(f(req, std::forward(args)...)); + res.end(); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + black_magic::CallHelper>::value, + void>::type + wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(res, std::forward(args)...); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + black_magic::CallHelper>::value, + void>::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(req, res, std::forward(args)...); + } + + // wrapped_handler_call transparently wraps a handler call behind (req, res, args...) + template + typename std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value, + void>::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(req, res, std::forward(args)...); + } + + template + struct middleware_call_criteria_dynamic + {}; + + template<> + struct middleware_call_criteria_dynamic + { + middleware_call_criteria_dynamic(const std::vector& indices): + indices(indices), slider(0) {} + + template + bool enabled(int mw_index) const + { + if (slider < int(indices.size()) && indices[slider] == mw_index) + { + slider++; + return true; + } + return false; + } + + private: + const std::vector& indices; + mutable int slider; + }; + + template<> + struct middleware_call_criteria_dynamic + { + middleware_call_criteria_dynamic(const std::vector& indices): + indices(indices), slider(int(indices.size()) - 1) {} + + template + bool enabled(int mw_index) const + { + if (slider >= 0 && indices[slider] == mw_index) + { + slider--; + return true; + } + return false; + } + + private: + const std::vector& indices; + mutable int slider; + }; + + } // namespace detail +} // namespace crow + + + +namespace crow +{ + namespace detail + { + + + template + struct partial_context : public black_magic::pop_back::template rebind, public black_magic::last_element_type::type::context + { + using parent_context = typename black_magic::pop_back::template rebind<::crow::detail::partial_context>; + template + using partial = typename std::conditional>::type; + + template + typename T::context& get() + { + return static_cast(*this); + } + }; + + + + template<> + struct partial_context<> + { + template + using partial = partial_context; + }; + + + template + struct context : private partial_context + //struct context : private Middlewares::context... // simple but less type-safe + { + template + friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res); + template + friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res); + + template + friend typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type + middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx); + + template + typename T::context& get() + { + return static_cast(*this); + } + + template + using partial = typename partial_context::template partial; + }; + } // namespace detail +} // namespace crow + + +#ifdef CROW_USE_BOOST +#include +#include +#else +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif +#include +#include +#endif + +#include +#include +#include +#include + + +namespace crow +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; + using error_code = boost::system::error_code; +#else + using error_code = asio::error_code; +#endif + namespace detail + { + + /// A class for scheduling functions to be called after a specific amount of ticks. A tick is equal to 1 second. + class task_timer + { + public: + using task_type = std::function; + using identifier_type = size_t; + + private: + using clock_type = std::chrono::steady_clock; + using time_type = clock_type::time_point; + + public: + task_timer(asio::io_service& io_service): + io_service_(io_service), timer_(io_service_) + { + timer_.expires_after(std::chrono::seconds(1)); + timer_.async_wait( + std::bind(&task_timer::tick_handler, this, std::placeholders::_1)); + } + + ~task_timer() { timer_.cancel(); } + + void cancel(identifier_type id) + { + tasks_.erase(id); + CROW_LOG_DEBUG << "task_timer cancelled: " << this << ' ' << id; + } + + /// Schedule the given task to be executed after the default amount of ticks. + + /// + /// \return identifier_type Used to cancel the thread. + /// It is not bound to this task_timer instance and in some cases could lead to + /// undefined behavior if used with other task_timer objects or after the task + /// has been successfully executed. + identifier_type schedule(const task_type& task) + { + tasks_.insert( + {++highest_id_, + {clock_type::now() + std::chrono::seconds(get_default_timeout()), + task}}); + CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_; + return highest_id_; + } + + /// Schedule the given task to be executed after the given time. + + /// + /// \param timeout The amount of ticks (seconds) to wait before execution. + /// + /// \return identifier_type Used to cancel the thread. + /// It is not bound to this task_timer instance and in some cases could lead to + /// undefined behavior if used with other task_timer objects or after the task + /// has been successfully executed. + identifier_type schedule(const task_type& task, std::uint8_t timeout) + { + tasks_.insert({++highest_id_, + {clock_type::now() + std::chrono::seconds(timeout), task}}); + CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_; + return highest_id_; + } + + /// Set the default timeout for this task_timer instance. (Default: 5) + + /// + /// \param timeout The amount of ticks (seconds) to wait before execution. + void set_default_timeout(std::uint8_t timeout) { default_timeout_ = timeout; } + + /// Get the default timeout. (Default: 5) + std::uint8_t get_default_timeout() const { return default_timeout_; } + + private: + void process_tasks() + { + time_type current_time = clock_type::now(); + std::vector finished_tasks; + + for (const auto& task : tasks_) + { + if (task.second.first < current_time) + { + (task.second.second)(); + finished_tasks.push_back(task.first); + CROW_LOG_DEBUG << "task_timer called: " << this << ' ' << task.first; + } + } + + for (const auto& task : finished_tasks) + tasks_.erase(task); + + // If no task is currently scheduled, reset the issued ids back to 0. + if (tasks_.empty()) highest_id_ = 0; + } + + void tick_handler(const error_code& ec) + { + if (ec) return; + + process_tasks(); + + timer_.expires_after(std::chrono::seconds(1)); + timer_.async_wait( + std::bind(&task_timer::tick_handler, this, std::placeholders::_1)); + } + + private: + std::uint8_t default_timeout_{5}; + asio::io_service& io_service_; + asio::basic_waitable_timer timer_; + std::map> tasks_; + + // A continuosly increasing number to be issued to threads to identify them. + // If no tasks are scheduled, it will be reset to 0. + identifier_type highest_id_{0}; + }; + } // namespace detail +} // namespace crow + + +#ifdef CROW_USE_BOOST +#include +#else +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif +#include +#endif + +#include +#include +#include +#include +#include + + +namespace crow +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; + using error_code = boost::system::error_code; +#else + using error_code = asio::error_code; +#endif + using tcp = asio::ip::tcp; + +#ifdef CROW_ENABLE_DEBUG + static std::atomic connectionCount; +#endif + + /// An HTTP connection. + template + class Connection: public std::enable_shared_from_this> + { + friend struct crow::response; + + public: + Connection( + asio::io_service& io_service, + Handler* handler, + const std::string& server_name, + std::tuple* middlewares, + std::function& get_cached_date_str_f, + detail::task_timer& task_timer, + typename Adaptor::context* adaptor_ctx_, + std::atomic& queue_length): + adaptor_(io_service, adaptor_ctx_), + handler_(handler), + parser_(this), + req_(parser_.req), + server_name_(server_name), + middlewares_(middlewares), + get_cached_date_str(get_cached_date_str_f), + task_timer_(task_timer), + res_stream_threshold_(handler->stream_threshold()), + queue_length_(queue_length) + { +#ifdef CROW_ENABLE_DEBUG + connectionCount++; + CROW_LOG_DEBUG << "Connection (" << this << ") allocated, total: " << connectionCount; +#endif + } + + ~Connection() + { +#ifdef CROW_ENABLE_DEBUG + connectionCount--; + CROW_LOG_DEBUG << "Connection (" << this << ") freed, total: " << connectionCount; +#endif + } + + /// The TCP socket on top of which the connection is established. + decltype(std::declval().raw_socket())& socket() + { + return adaptor_.raw_socket(); + } + + void start() + { + auto self = this->shared_from_this(); + adaptor_.start([self](const error_code& ec) { + if (!ec) + { + self->start_deadline(); + self->parser_.clear(); + + self->do_read(); + } + else + { + CROW_LOG_ERROR << "Could not start adaptor: " << ec.message(); + } + }); + } + + void handle_url() + { + routing_handle_result_ = handler_->handle_initial(req_, res); + // if no route is found for the request method, return the response without parsing or processing anything further. + if (!routing_handle_result_->rule_index) + { + parser_.done(); + need_to_call_after_handlers_ = true; + complete_request(); + } + } + + void handle_header() + { + // HTTP 1.1 Expect: 100-continue + if (req_.http_ver_major == 1 && req_.http_ver_minor == 1 && get_header_value(req_.headers, "expect") == "100-continue") + { + continue_requested = true; + buffers_.clear(); + static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n"; + buffers_.emplace_back(expect_100_continue.data(), expect_100_continue.size()); + do_write(); + } + } + + void handle() + { + // TODO(EDev): cancel_deadline_timer should be looked into, it might be a good idea to add it to handle_url() and then restart the timer once everything passes + cancel_deadline_timer(); + bool is_invalid_request = false; + add_keep_alive_ = false; + + // Create context + ctx_ = detail::context(); + req_.middleware_context = static_cast(&ctx_); + req_.middleware_container = static_cast(middlewares_); + req_.io_service = &adaptor_.get_io_service(); + + req_.remote_ip_address = adaptor_.remote_endpoint().address().to_string(); + + add_keep_alive_ = req_.keep_alive; + close_connection_ = req_.close_connection; + + if (req_.check_version(1, 1)) // HTTP/1.1 + { + if (!req_.headers.count("host")) + { + is_invalid_request = true; + res = response(400); + } + else if (req_.upgrade) + { + // h2 or h2c headers + if (req_.get_header_value("upgrade").substr(0, 2) == "h2") + { + // TODO(ipkn): HTTP/2 + // currently, ignore upgrade header + } + else + { + + detail::middleware_call_helper({}, *middlewares_, req_, res, ctx_); + close_connection_ = true; + handler_->handle_upgrade(req_, res, std::move(adaptor_)); + return; + } + } + } + + CROW_LOG_INFO << "Request: " << utility::lexical_cast(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << (char)(req_.http_ver_major + '0') << "." << (char)(req_.http_ver_minor + '0') << ' ' << method_name(req_.method) << " " << req_.url; + + + need_to_call_after_handlers_ = false; + if (!is_invalid_request) + { + res.complete_request_handler_ = nullptr; + auto self = this->shared_from_this(); + res.is_alive_helper_ = [self]() -> bool { + return self->adaptor_.is_open(); + }; + + detail::middleware_call_helper({}, *middlewares_, req_, res, ctx_); + + if (!res.completed_) + { + auto self = this->shared_from_this(); + res.complete_request_handler_ = [self] { + self->complete_request(); + }; + need_to_call_after_handlers_ = true; + handler_->handle(req_, res, routing_handle_result_); + if (add_keep_alive_) + res.set_header("connection", "Keep-Alive"); + } + else + { + complete_request(); + } + } + else + { + complete_request(); + } + } + + /// Call the after handle middleware and send the write the response to the connection. + void complete_request() + { + CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_; + res.is_alive_helper_ = nullptr; + + if (need_to_call_after_handlers_) + { + need_to_call_after_handlers_ = false; + + // call all after_handler of middlewares + detail::after_handlers_call_helper< + detail::middleware_call_criteria_only_global, + (static_cast(sizeof...(Middlewares)) - 1), + decltype(ctx_), + decltype(*middlewares_)>({}, *middlewares_, ctx_, req_, res); + } +#ifdef CROW_ENABLE_COMPRESSION + if (handler_->compression_used()) + { + std::string accept_encoding = req_.get_header_value("Accept-Encoding"); + if (!accept_encoding.empty() && res.compressed) + { + switch (handler_->compression_algorithm()) + { + case compression::DEFLATE: + if (accept_encoding.find("deflate") != std::string::npos) + { + res.body = compression::compress_string(res.body, compression::algorithm::DEFLATE); + res.set_header("Content-Encoding", "deflate"); + } + break; + case compression::GZIP: + if (accept_encoding.find("gzip") != std::string::npos) + { + res.body = compression::compress_string(res.body, compression::algorithm::GZIP); + res.set_header("Content-Encoding", "gzip"); + } + break; + default: + break; + } + } + } +#endif + //if there is a redirection with a partial URL, treat the URL as a route. + std::string location = res.get_header_value("Location"); + if (!location.empty() && location.find("://", 0) == std::string::npos) + { +#ifdef CROW_ENABLE_SSL + if (handler_->ssl_used()) + location.insert(0, "https://" + req_.get_header_value("Host")); + else +#endif + location.insert(0, "http://" + req_.get_header_value("Host")); + res.set_header("location", location); + } + + prepare_buffers(); + + if (res.is_static_type()) + { + do_write_static(); + } + else + { + do_write_general(); + } + } + + private: + void prepare_buffers() + { + res.complete_request_handler_ = nullptr; + res.is_alive_helper_ = nullptr; + + if (!adaptor_.is_open()) + { + //CROW_LOG_DEBUG << this << " delete (socket is closed) " << is_reading << ' ' << is_writing; + //delete this; + return; + } + // TODO(EDev): HTTP version in status codes should be dynamic + // Keep in sync with common.h/status + static std::unordered_map statusCodes = { + {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"}, + {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"}, + + {status::OK, "HTTP/1.1 200 OK\r\n"}, + {status::CREATED, "HTTP/1.1 201 Created\r\n"}, + {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"}, + {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"}, + {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"}, + {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"}, + {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"}, + + {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"}, + {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"}, + {status::FOUND, "HTTP/1.1 302 Found\r\n"}, + {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"}, + {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"}, + {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"}, + {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"}, + + {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"}, + {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"}, + {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"}, + {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"}, + {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"}, + {status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"}, + {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"}, + {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"}, + {status::GONE, "HTTP/1.1 410 Gone\r\n"}, + {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"}, + {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"}, + {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"}, + {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"}, + {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"}, + {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"}, + {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"}, + + {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"}, + {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"}, + {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"}, + {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"}, + {status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"}, + {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"}, + }; + + static const std::string seperator = ": "; + + buffers_.clear(); + buffers_.reserve(4 * (res.headers.size() + 5) + 3); + + if (!statusCodes.count(res.code)) + { + CROW_LOG_WARNING << this << " status code " + << "(" << res.code << ")" + << " not defined, returning 500 instead"; + res.code = 500; + } + + auto& status = statusCodes.find(res.code)->second; + buffers_.emplace_back(status.data(), status.size()); + + if (res.code >= 400 && res.body.empty()) + res.body = statusCodes[res.code].substr(9); + + for (auto& kv : res.headers) + { + buffers_.emplace_back(kv.first.data(), kv.first.size()); + buffers_.emplace_back(seperator.data(), seperator.size()); + buffers_.emplace_back(kv.second.data(), kv.second.size()); + buffers_.emplace_back(crlf.data(), crlf.size()); + } + + if (!res.manual_length_header && !res.headers.count("content-length")) + { + content_length_ = std::to_string(res.body.size()); + static std::string content_length_tag = "Content-Length: "; + buffers_.emplace_back(content_length_tag.data(), content_length_tag.size()); + buffers_.emplace_back(content_length_.data(), content_length_.size()); + buffers_.emplace_back(crlf.data(), crlf.size()); + } + if (!res.headers.count("server")) + { + static std::string server_tag = "Server: "; + buffers_.emplace_back(server_tag.data(), server_tag.size()); + buffers_.emplace_back(server_name_.data(), server_name_.size()); + buffers_.emplace_back(crlf.data(), crlf.size()); + } + if (!res.headers.count("date")) + { + static std::string date_tag = "Date: "; + date_str_ = get_cached_date_str(); + buffers_.emplace_back(date_tag.data(), date_tag.size()); + buffers_.emplace_back(date_str_.data(), date_str_.size()); + buffers_.emplace_back(crlf.data(), crlf.size()); + } + if (add_keep_alive_) + { + static std::string keep_alive_tag = "Connection: Keep-Alive"; + buffers_.emplace_back(keep_alive_tag.data(), keep_alive_tag.size()); + buffers_.emplace_back(crlf.data(), crlf.size()); + } + + buffers_.emplace_back(crlf.data(), crlf.size()); + } + + void do_write_static() + { + asio::write(adaptor_.socket(), buffers_); + + if (res.file_info.statResult == 0) + { + std::ifstream is(res.file_info.path.c_str(), std::ios::in | std::ios::binary); + std::vector buffers{1}; + char buf[16384]; + is.read(buf, sizeof(buf)); + while (is.gcount() > 0) + { + buffers[0] = asio::buffer(buf, is.gcount()); + do_write_sync(buffers); + is.read(buf, sizeof(buf)); + } + } + if (close_connection_) + { + adaptor_.shutdown_readwrite(); + adaptor_.close(); + CROW_LOG_DEBUG << this << " from write (static)"; + } + + res.end(); + res.clear(); + buffers_.clear(); + parser_.clear(); + } + + void do_write_general() + { + if (res.body.length() < res_stream_threshold_) + { + res_body_copy_.swap(res.body); + buffers_.emplace_back(res_body_copy_.data(), res_body_copy_.size()); + + do_write(); + + if (need_to_start_read_after_complete_) + { + need_to_start_read_after_complete_ = false; + start_deadline(); + do_read(); + } + } + else + { + asio::write(adaptor_.socket(), buffers_); // Write the response start / headers + cancel_deadline_timer(); + if (res.body.length() > 0) + { + std::vector buffers{1}; + const uint8_t *data = reinterpret_cast(res.body.data()); + size_t length = res.body.length(); + for(size_t transferred = 0; transferred < length;) + { + size_t to_transfer = CROW_MIN(16384UL, length-transferred); + buffers[0] = asio::const_buffer(data+transferred, to_transfer); + do_write_sync(buffers); + transferred += to_transfer; + } + } + if (close_connection_) + { + adaptor_.shutdown_readwrite(); + adaptor_.close(); + CROW_LOG_DEBUG << this << " from write (res_stream)"; + } + + res.end(); + res.clear(); + buffers_.clear(); + parser_.clear(); + } + } + + void do_read() + { + auto self = this->shared_from_this(); + adaptor_.socket().async_read_some( + asio::buffer(buffer_), + [self](const error_code& ec, std::size_t bytes_transferred) { + bool error_while_reading = true; + if (!ec) + { + bool ret = self->parser_.feed(self->buffer_.data(), bytes_transferred); + if (ret && self->adaptor_.is_open()) + { + error_while_reading = false; + } + } + + if (error_while_reading) + { + self->cancel_deadline_timer(); + self->parser_.done(); + self->adaptor_.shutdown_read(); + self->adaptor_.close(); + CROW_LOG_DEBUG << self << " from read(1) with description: \"" << http_errno_description(static_cast(self->parser_.http_errno)) << '\"'; + } + else if (self->close_connection_) + { + self->cancel_deadline_timer(); + self->parser_.done(); + // adaptor will close after write + } + else if (!self->need_to_call_after_handlers_) + { + self->start_deadline(); + self->do_read(); + } + else + { + // res will be completed later by user + self->need_to_start_read_after_complete_ = true; + } + }); + } + + void do_write() + { + auto self = this->shared_from_this(); + asio::async_write( + adaptor_.socket(), buffers_, + [self](const error_code& ec, std::size_t /*bytes_transferred*/) { + self->res.clear(); + self->res_body_copy_.clear(); + if (!self->continue_requested) + { + self->parser_.clear(); + } + else + { + self->continue_requested = false; + } + + if (!ec) + { + if (self->close_connection_) + { + self->adaptor_.shutdown_write(); + self->adaptor_.close(); + CROW_LOG_DEBUG << self << " from write(1)"; + } + } + else + { + CROW_LOG_DEBUG << self << " from write(2)"; + } + }); + } + + inline void do_write_sync(std::vector& buffers) + { + + asio::write(adaptor_.socket(), buffers, [&](error_code ec, std::size_t) { + if (!ec) + { + return false; + } + else + { + CROW_LOG_ERROR << ec << " - happened while sending buffers"; + CROW_LOG_DEBUG << this << " from write (sync)(2)"; + return true; + } + }); + } + + void cancel_deadline_timer() + { + CROW_LOG_DEBUG << this << " timer cancelled: " << &task_timer_ << ' ' << task_id_; + task_timer_.cancel(task_id_); + } + + void start_deadline(/*int timeout = 5*/) + { + cancel_deadline_timer(); + + auto self = this->shared_from_this(); + task_id_ = task_timer_.schedule([self] { + if (!self->adaptor_.is_open()) + { + return; + } + self->adaptor_.shutdown_readwrite(); + self->adaptor_.close(); + }); + CROW_LOG_DEBUG << this << " timer added: " << &task_timer_ << ' ' << task_id_; + } + + private: + Adaptor adaptor_; + Handler* handler_; + + std::array buffer_; + + HTTPParser parser_; + std::unique_ptr routing_handle_result_; + request& req_; + response res; + + bool close_connection_ = false; + + const std::string& server_name_; + std::vector buffers_; + + std::string content_length_; + std::string date_str_; + std::string res_body_copy_; + + detail::task_timer::identifier_type task_id_{}; + + bool continue_requested{}; + bool need_to_call_after_handlers_{}; + bool need_to_start_read_after_complete_{}; + bool add_keep_alive_{}; + + std::tuple* middlewares_; + detail::context ctx_; + + std::function& get_cached_date_str; + detail::task_timer& task_timer_; + + size_t res_stream_threshold_; + + std::atomic& queue_length_; + }; + +} // namespace crow + +#include + +namespace crow // NOTE: Already documented in "crow/app.h" +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; + using error_code = boost::system::error_code; +#else + using error_code = asio::error_code; +#endif + + /** + * \namespace crow::websocket + * \brief Namespace that includes the \ref Connection class + * and \ref connection struct. Useful for WebSockets connection. + * + * Used specially in crow/websocket.h, crow/app.h and crow/routing.h + */ + namespace websocket + { + enum class WebSocketReadState + { + MiniHeader, + Len16, + Len64, + Mask, + Payload, + }; + + /// A base class for websocket connection. + struct connection + { + virtual void send_binary(std::string msg) = 0; + virtual void send_text(std::string msg) = 0; + virtual void send_ping(std::string msg) = 0; + virtual void send_pong(std::string msg) = 0; + virtual void close(std::string const& msg = "quit") = 0; + virtual std::string get_remote_ip() = 0; + virtual ~connection() = default; + + void userdata(void* u) { userdata_ = u; } + void* userdata() { return userdata_; } + + private: + void* userdata_; + }; + + // Modified version of the illustration in RFC6455 Section-5.2 + // + // + // 0 1 2 3 -byte + // 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 -bit + // +-+-+-+-+-------+-+-------------+-------------------------------+ + // |F|R|R|R| opcode|M| Payload len | Extended payload length | + // |I|S|S|S| (4) |A| (7) | (16/64) | + // |N|V|V|V| |S| | (if payload len==126/127) | + // | |1|2|3| |K| | | + // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + // | Extended payload length continued, if payload len == 127 | + // + - - - - - - - - - - - - - - - +-------------------------------+ + // | |Masking-key, if MASK set to 1 | + // +-------------------------------+-------------------------------+ + // | Masking-key (continued) | Payload Data | + // +-------------------------------- - - - - - - - - - - - - - - - + + // : Payload Data continued ... : + // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // | Payload Data continued ... | + // +---------------------------------------------------------------+ + // + + /// A websocket connection. + + template + class Connection : public connection + { + public: + /// Constructor for a connection. + + /// + /// Requires a request with an "Upgrade: websocket" header.
+ /// Automatically handles the handshake. + Connection(const crow::request& req, Adaptor&& adaptor, Handler* handler, uint64_t max_payload, + std::function open_handler, + std::function message_handler, + std::function close_handler, + std::function error_handler, + std::function accept_handler): + adaptor_(std::move(adaptor)), + handler_(handler), + max_payload_bytes_(max_payload), + open_handler_(std::move(open_handler)), + message_handler_(std::move(message_handler)), + close_handler_(std::move(close_handler)), + error_handler_(std::move(error_handler)), + accept_handler_(std::move(accept_handler)) + { + if (!utility::string_equals(req.get_header_value("upgrade"), "websocket")) + { + adaptor_.close(); + handler_->remove_websocket(this); + delete this; + return; + } + + if (accept_handler_) + { + void* ud = nullptr; + if (!accept_handler_(req, &ud)) + { + adaptor_.close(); + handler_->remove_websocket(this); + delete this; + return; + } + userdata(ud); + } + + // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + // Sec-WebSocket-Version: 13 + std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + sha1::SHA1 s; + s.processBytes(magic.data(), magic.size()); + uint8_t digest[20]; + s.getDigestBytes(digest); + + start(crow::utility::base64encode((unsigned char*)digest, 20)); + } + + ~Connection() noexcept override + { + // Do not modify anchor_ here since writing shared_ptr is not atomic. + auto watch = std::weak_ptr{anchor_}; + + // Wait until all unhandled asynchronous operations to join. + // As the deletion occurs inside 'check_destroy()', which already locks + // anchor, use count can be 1 on valid deletion context. + while (watch.use_count() > 2) // 1 for 'check_destroy() routine', 1 for 'this->anchor_' + { + std::this_thread::yield(); + } + } + + template + struct WeakWrappedMessage + { + Callable callable; + std::weak_ptr watch; + + void operator()() + { + if (auto anchor = watch.lock()) + { + std::move(callable)(); + } + } + }; + + /// Send data through the socket. + template + void dispatch(CompletionHandler&& handler) + { + asio::dispatch(adaptor_.get_io_service(), + WeakWrappedMessage::type>{ + std::forward(handler), anchor_}); + } + + /// Send data through the socket and return immediately. + template + void post(CompletionHandler&& handler) + { + asio::post(adaptor_.get_io_service(), + WeakWrappedMessage::type>{ + std::forward(handler), anchor_}); + } + + /// Send a "Ping" message. + + /// + /// Usually invoked to check if the other point is still online. + void send_ping(std::string msg) override + { + send_data(0x9, std::move(msg)); + } + + /// Send a "Pong" message. + + /// + /// Usually automatically invoked as a response to a "Ping" message. + void send_pong(std::string msg) override + { + send_data(0xA, std::move(msg)); + } + + /// Send a binary encoded message. + void send_binary(std::string msg) override + { + send_data(0x2, std::move(msg)); + } + + /// Send a plaintext message. + void send_text(std::string msg) override + { + send_data(0x1, std::move(msg)); + } + + /// Send a close signal. + + /// + /// Sets a flag to destroy the object once the message is sent. + void close(std::string const& msg) override + { + dispatch([this, msg]() mutable { + has_sent_close_ = true; + if (has_recv_close_ && !is_close_handler_called_) + { + is_close_handler_called_ = true; + if (close_handler_) + close_handler_(*this, msg); + } + auto header = build_header(0x8, msg.size()); + write_buffers_.emplace_back(std::move(header)); + write_buffers_.emplace_back(msg); + do_write(); + }); + } + + std::string get_remote_ip() override + { + return adaptor_.remote_endpoint().address().to_string(); + } + + void set_max_payload_size(uint64_t payload) + { + max_payload_bytes_ = payload; + } + + protected: + /// Generate the websocket headers using an opcode and the message size (in bytes). + std::string build_header(int opcode, size_t size) + { + char buf[2 + 8] = "\x80\x00"; + buf[0] += opcode; + if (size < 126) + { + buf[1] += static_cast(size); + return {buf, buf + 2}; + } + else if (size < 0x10000) + { + buf[1] += 126; + *(uint16_t*)(buf + 2) = htons(static_cast(size)); + return {buf, buf + 4}; + } + else + { + buf[1] += 127; + *reinterpret_cast(buf + 2) = ((1 == htonl(1)) ? static_cast(size) : (static_cast(htonl((size)&0xFFFFFFFF)) << 32) | htonl(static_cast(size) >> 32)); + return {buf, buf + 10}; + } + } + + /// Send the HTTP upgrade response. + + /// + /// Finishes the handshake process, then starts reading messages from the socket. + void start(std::string&& hello) + { + static const std::string header = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: "; + write_buffers_.emplace_back(header); + write_buffers_.emplace_back(std::move(hello)); + write_buffers_.emplace_back(crlf); + write_buffers_.emplace_back(crlf); + do_write(); + if (open_handler_) + open_handler_(*this); + do_read(); + } + + /// Read a websocket message. + + /// + /// Involves:
+ /// Handling headers (opcodes, size).
+ /// Unmasking the payload.
+ /// Reading the actual payload.
+ void do_read() + { + if (has_sent_close_ && has_recv_close_) + { + close_connection_ = true; + adaptor_.shutdown_readwrite(); + adaptor_.close(); + check_destroy(); + return; + } + + is_reading = true; + switch (state_) + { + case WebSocketReadState::MiniHeader: + { + mini_header_ = 0; + //asio::async_read(adaptor_.socket(), asio::buffer(&mini_header_, 1), + adaptor_.socket().async_read_some( + asio::buffer(&mini_header_, 2), + [this](const error_code& ec, std::size_t +#ifdef CROW_ENABLE_DEBUG + bytes_transferred +#endif + ) + + { + is_reading = false; + mini_header_ = ntohs(mini_header_); +#ifdef CROW_ENABLE_DEBUG + + if (!ec && bytes_transferred != 2) + { + throw std::runtime_error("WebSocket:MiniHeader:async_read fail:asio bug?"); + } +#endif + + if (!ec) + { + if ((mini_header_ & 0x80) == 0x80) + has_mask_ = true; + else //if the websocket specification is enforced and the message isn't masked, terminate the connection + { +#ifndef CROW_ENFORCE_WS_SPEC + has_mask_ = false; +#else + close_connection_ = true; + adaptor_.shutdown_readwrite(); + adaptor_.close(); + if (error_handler_) + error_handler_(*this, "Client connection not masked."); + check_destroy(); +#endif + } + + if ((mini_header_ & 0x7f) == 127) + { + state_ = WebSocketReadState::Len64; + } + else if ((mini_header_ & 0x7f) == 126) + { + state_ = WebSocketReadState::Len16; + } + else + { + remaining_length_ = mini_header_ & 0x7f; + state_ = WebSocketReadState::Mask; + } + do_read(); + } + else + { + close_connection_ = true; + adaptor_.shutdown_readwrite(); + adaptor_.close(); + if (error_handler_) + error_handler_(*this, ec.message()); + check_destroy(); + } + }); + } + break; + case WebSocketReadState::Len16: + { + remaining_length_ = 0; + remaining_length16_ = 0; + asio::async_read( + adaptor_.socket(), asio::buffer(&remaining_length16_, 2), + [this](const error_code& ec, std::size_t +#ifdef CROW_ENABLE_DEBUG + bytes_transferred +#endif + ) { + is_reading = false; + remaining_length16_ = ntohs(remaining_length16_); + remaining_length_ = remaining_length16_; +#ifdef CROW_ENABLE_DEBUG + if (!ec && bytes_transferred != 2) + { + throw std::runtime_error("WebSocket:Len16:async_read fail:asio bug?"); + } +#endif + + if (!ec) + { + state_ = WebSocketReadState::Mask; + do_read(); + } + else + { + close_connection_ = true; + adaptor_.shutdown_readwrite(); + adaptor_.close(); + if (error_handler_) + error_handler_(*this, ec.message()); + check_destroy(); + } + }); + } + break; + case WebSocketReadState::Len64: + { + asio::async_read( + adaptor_.socket(), asio::buffer(&remaining_length_, 8), + [this](const error_code& ec, std::size_t +#ifdef CROW_ENABLE_DEBUG + bytes_transferred +#endif + ) { + is_reading = false; + remaining_length_ = ((1 == ntohl(1)) ? (remaining_length_) : (static_cast(ntohl((remaining_length_)&0xFFFFFFFF)) << 32) | ntohl((remaining_length_) >> 32)); +#ifdef CROW_ENABLE_DEBUG + if (!ec && bytes_transferred != 8) + { + throw std::runtime_error("WebSocket:Len16:async_read fail:asio bug?"); + } +#endif + + if (!ec) + { + state_ = WebSocketReadState::Mask; + do_read(); + } + else + { + close_connection_ = true; + adaptor_.shutdown_readwrite(); + adaptor_.close(); + if (error_handler_) + error_handler_(*this, ec.message()); + check_destroy(); + } + }); + } + break; + case WebSocketReadState::Mask: + if (remaining_length_ > max_payload_bytes_) + { + close_connection_ = true; + adaptor_.close(); + if (error_handler_) + error_handler_(*this, "Message length exceeds maximum payload."); + check_destroy(); + } + else if (has_mask_) + { + asio::async_read( + adaptor_.socket(), asio::buffer((char*)&mask_, 4), + [this](const error_code& ec, std::size_t +#ifdef CROW_ENABLE_DEBUG + bytes_transferred +#endif + ) { + is_reading = false; +#ifdef CROW_ENABLE_DEBUG + if (!ec && bytes_transferred != 4) + { + throw std::runtime_error("WebSocket:Mask:async_read fail:asio bug?"); + } +#endif + + if (!ec) + { + state_ = WebSocketReadState::Payload; + do_read(); + } + else + { + close_connection_ = true; + if (error_handler_) + error_handler_(*this, ec.message()); + adaptor_.shutdown_readwrite(); + adaptor_.close(); + check_destroy(); + } + }); + } + else + { + state_ = WebSocketReadState::Payload; + do_read(); + } + break; + case WebSocketReadState::Payload: + { + auto to_read = static_cast(buffer_.size()); + if (remaining_length_ < to_read) + to_read = remaining_length_; + adaptor_.socket().async_read_some( + asio::buffer(buffer_, static_cast(to_read)), + [this](const error_code& ec, std::size_t bytes_transferred) { + is_reading = false; + + if (!ec) + { + fragment_.insert(fragment_.end(), buffer_.begin(), buffer_.begin() + bytes_transferred); + remaining_length_ -= bytes_transferred; + if (remaining_length_ == 0) + { + if (handle_fragment()) + { + state_ = WebSocketReadState::MiniHeader; + do_read(); + } + } + else + do_read(); + } + else + { + close_connection_ = true; + if (error_handler_) + error_handler_(*this, ec.message()); + adaptor_.shutdown_readwrite(); + adaptor_.close(); + check_destroy(); + } + }); + } + break; + } + } + + /// Check if the FIN bit is set. + bool is_FIN() + { + return mini_header_ & 0x8000; + } + + /// Extract the opcode from the header. + int opcode() + { + return (mini_header_ & 0x0f00) >> 8; + } + + /// Process the payload fragment. + + /// + /// Unmasks the fragment, checks the opcode, merges fragments into 1 message body, and calls the appropriate handler. + bool handle_fragment() + { + if (has_mask_) + { + for (decltype(fragment_.length()) i = 0; i < fragment_.length(); i++) + { + fragment_[i] ^= ((char*)&mask_)[i % 4]; + } + } + switch (opcode()) + { + case 0: // Continuation + { + message_ += fragment_; + if (is_FIN()) + { + if (message_handler_) + message_handler_(*this, message_, is_binary_); + message_.clear(); + } + } + break; + case 1: // Text + { + is_binary_ = false; + message_ += fragment_; + if (is_FIN()) + { + if (message_handler_) + message_handler_(*this, message_, is_binary_); + message_.clear(); + } + } + break; + case 2: // Binary + { + is_binary_ = true; + message_ += fragment_; + if (is_FIN()) + { + if (message_handler_) + message_handler_(*this, message_, is_binary_); + message_.clear(); + } + } + break; + case 0x8: // Close + { + has_recv_close_ = true; + if (!has_sent_close_) + { + close(fragment_); + } + else + { + adaptor_.shutdown_readwrite(); + adaptor_.close(); + close_connection_ = true; + if (!is_close_handler_called_) + { + if (close_handler_) + close_handler_(*this, fragment_); + is_close_handler_called_ = true; + } + check_destroy(); + return false; + } + } + break; + case 0x9: // Ping + { + send_pong(fragment_); + } + break; + case 0xA: // Pong + { + pong_received_ = true; + } + break; + } + + fragment_.clear(); + return true; + } + + /// Send the buffers' data through the socket. + + /// + /// Also destroys the object if the Close flag is set. + void do_write() + { + if (sending_buffers_.empty()) + { + sending_buffers_.swap(write_buffers_); + std::vector buffers; + buffers.reserve(sending_buffers_.size()); + for (auto& s : sending_buffers_) + { + buffers.emplace_back(asio::buffer(s)); + } + auto watch = std::weak_ptr{anchor_}; + asio::async_write( + adaptor_.socket(), buffers, + [&, watch](const error_code& ec, std::size_t /*bytes_transferred*/) { + if (!ec && !close_connection_) + { + sending_buffers_.clear(); + if (!write_buffers_.empty()) + do_write(); + if (has_sent_close_) + close_connection_ = true; + } + else + { + auto anchor = watch.lock(); + if (anchor == nullptr) { return; } + + sending_buffers_.clear(); + close_connection_ = true; + check_destroy(); + } + }); + } + } + + /// Destroy the Connection. + void check_destroy() + { + //if (has_sent_close_ && has_recv_close_) + if (!is_close_handler_called_) + if (close_handler_) + close_handler_(*this, "uncleanly"); + handler_->remove_websocket(this); + if (sending_buffers_.empty() && !is_reading) + delete this; + } + + + struct SendMessageType + { + std::string payload; + Connection* self; + int opcode; + + void operator()() + { + self->send_data_impl(this); + } + }; + + void send_data_impl(SendMessageType* s) + { + auto header = build_header(s->opcode, s->payload.size()); + write_buffers_.emplace_back(std::move(header)); + write_buffers_.emplace_back(std::move(s->payload)); + do_write(); + } + + void send_data(int opcode, std::string&& msg) + { + SendMessageType event_arg{ + std::move(msg), + this, + opcode}; + + post(std::move(event_arg)); + } + + private: + Adaptor adaptor_; + Handler* handler_; + + std::vector sending_buffers_; + std::vector write_buffers_; + + std::array buffer_; + bool is_binary_; + std::string message_; + std::string fragment_; + WebSocketReadState state_{WebSocketReadState::MiniHeader}; + uint16_t remaining_length16_{0}; + uint64_t remaining_length_{0}; + uint64_t max_payload_bytes_{UINT64_MAX}; + bool close_connection_{false}; + bool is_reading{false}; + bool has_mask_{false}; + uint32_t mask_; + uint16_t mini_header_; + bool has_sent_close_{false}; + bool has_recv_close_{false}; + bool error_occurred_{false}; + bool pong_received_{false}; + bool is_close_handler_called_{false}; + + std::shared_ptr anchor_ = std::make_shared(); // Value is just for placeholding + + std::function open_handler_; + std::function message_handler_; + std::function close_handler_; + std::function error_handler_; + std::function accept_handler_; + }; + } // namespace websocket +} // namespace crow + + +namespace crow +{ + constexpr const char VERSION[] = "master"; +} + + +#ifdef CROW_USE_BOOST +#include +#ifdef CROW_ENABLE_SSL +#include +#endif +#else +#ifndef ASIO_STANDALONE +#define ASIO_STANDALONE +#endif +#include +#ifdef CROW_ENABLE_SSL +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include + + + +namespace crow // NOTE: Already documented in "crow/app.h" +{ +#ifdef CROW_USE_BOOST + namespace asio = boost::asio; + using error_code = boost::system::error_code; +#else + using error_code = asio::error_code; +#endif + using tcp = asio::ip::tcp; + + template + class Server + { + public: + Server(Handler* handler, std::string bindaddr, uint16_t port, std::string server_name = std::string("Crow/") + VERSION, std::tuple* middlewares = nullptr, uint16_t concurrency = 1, uint8_t timeout = 5, typename Adaptor::context* adaptor_ctx = nullptr): + acceptor_(io_service_, tcp::endpoint(asio::ip::address::from_string(bindaddr), port)), + signals_(io_service_), + tick_timer_(io_service_), + handler_(handler), + concurrency_(concurrency), + timeout_(timeout), + server_name_(server_name), + port_(port), + bindaddr_(bindaddr), + task_queue_length_pool_(concurrency_ - 1), + middlewares_(middlewares), + adaptor_ctx_(adaptor_ctx) + {} + + void set_tick_function(std::chrono::milliseconds d, std::function f) + { + tick_interval_ = d; + tick_function_ = f; + } + + void on_tick() + { + tick_function_(); + tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count())); + tick_timer_.async_wait([this](const error_code& ec) { + if (ec) + return; + on_tick(); + }); + } + + void run() + { + uint16_t worker_thread_count = concurrency_ - 1; + for (int i = 0; i < worker_thread_count; i++) + io_service_pool_.emplace_back(new asio::io_service()); + get_cached_date_str_pool_.resize(worker_thread_count); + task_timer_pool_.resize(worker_thread_count); + + std::vector> v; + std::atomic init_count(0); + for (uint16_t i = 0; i < worker_thread_count; i++) + v.push_back( + std::async( + std::launch::async, [this, i, &init_count] { + // thread local date string get function + auto last = std::chrono::steady_clock::now(); + + std::string date_str; + auto update_date_str = [&] { + auto last_time_t = time(0); + tm my_tm; + +#if defined(_MSC_VER) || defined(__MINGW32__) + gmtime_s(&my_tm, &last_time_t); +#else + gmtime_r(&last_time_t, &my_tm); +#endif + date_str.resize(100); + size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm); + date_str.resize(date_str_sz); + }; + update_date_str(); + get_cached_date_str_pool_[i] = [&]() -> std::string { + if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1)) + { + last = std::chrono::steady_clock::now(); + update_date_str(); + } + return date_str; + }; + + // initializing task timers + detail::task_timer task_timer(*io_service_pool_[i]); + task_timer.set_default_timeout(timeout_); + task_timer_pool_[i] = &task_timer; + task_queue_length_pool_[i] = 0; + + init_count++; + while (1) + { + try + { + if (io_service_pool_[i]->run() == 0) + { + // when io_service.run returns 0, there are no more works to do. + break; + } + } + catch (std::exception& e) + { + CROW_LOG_ERROR << "Worker Crash: An uncaught exception occurred: " << e.what(); + } + } + })); + + if (tick_function_ && tick_interval_.count() > 0) + { + tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count())); + tick_timer_.async_wait( + [this](const error_code& ec) { + if (ec) + return; + on_tick(); + }); + } + + port_ = acceptor_.local_endpoint().port(); + handler_->port(port_); + + + CROW_LOG_INFO << server_name_ << " server is running at " << (handler_->ssl_used() ? "https://" : "http://") << bindaddr_ << ":" << acceptor_.local_endpoint().port() << " using " << concurrency_ << " threads"; + CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs."; + + signals_.async_wait( + [&](const error_code& /*error*/, int /*signal_number*/) { + stop(); + }); + + while (worker_thread_count != init_count) + std::this_thread::yield(); + + do_accept(); + + std::thread( + [this] { + notify_start(); + io_service_.run(); + CROW_LOG_INFO << "Exiting."; + }) + .join(); + } + + void stop() + { + shutting_down_ = true; // Prevent the acceptor from taking new connections + for (auto& io_service : io_service_pool_) + { + if (io_service != nullptr) + { + CROW_LOG_INFO << "Closing IO service " << &io_service; + io_service->stop(); // Close all io_services (and HTTP connections) + } + } + + CROW_LOG_INFO << "Closing main IO service (" << &io_service_ << ')'; + io_service_.stop(); // Close main io_service + } + + /// Wait until the server has properly started + void wait_for_start() + { + std::unique_lock lock(start_mutex_); + if (!server_started_) + cv_started_.wait(lock); + } + + void signal_clear() + { + signals_.clear(); + } + + void signal_add(int signal_number) + { + signals_.add(signal_number); + } + + private: + uint16_t pick_io_service_idx() + { + uint16_t min_queue_idx = 0; + + // TODO improve load balancing + // size_t is used here to avoid the security issue https://codeql.github.com/codeql-query-help/cpp/cpp-comparison-with-wider-type/ + // even though the max value of this can be only uint16_t as concurrency is uint16_t. + for (size_t i = 1; i < task_queue_length_pool_.size() && task_queue_length_pool_[min_queue_idx] > 0; i++) + // No need to check other io_services if the current one has no tasks + { + if (task_queue_length_pool_[i] < task_queue_length_pool_[min_queue_idx]) + min_queue_idx = i; + } + return min_queue_idx; + } + + void do_accept() + { + if (!shutting_down_) + { + uint16_t service_idx = pick_io_service_idx(); + asio::io_service& is = *io_service_pool_[service_idx]; + task_queue_length_pool_[service_idx]++; + CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; + + auto p = std::make_shared>( + is, handler_, server_name_, middlewares_, + get_cached_date_str_pool_[service_idx], *task_timer_pool_[service_idx], adaptor_ctx_, task_queue_length_pool_[service_idx]); + + acceptor_.async_accept( + p->socket(), + [this, p, &is, service_idx](error_code ec) { + if (!ec) + { + is.post( + [p] { + p->start(); + }); + } + else + { + task_queue_length_pool_[service_idx]--; + CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; + } + do_accept(); + }); + } + } + + /// Notify anything using `wait_for_start()` to proceed + void notify_start() + { + std::unique_lock lock(start_mutex_); + server_started_ = true; + cv_started_.notify_all(); + } + + private: + std::vector> io_service_pool_; + asio::io_service io_service_; + std::vector task_timer_pool_; + std::vector> get_cached_date_str_pool_; + tcp::acceptor acceptor_; + bool shutting_down_ = false; + bool server_started_{false}; + std::condition_variable cv_started_; + std::mutex start_mutex_; + asio::signal_set signals_; + + asio::basic_waitable_timer tick_timer_; + + Handler* handler_; + uint16_t concurrency_{2}; + std::uint8_t timeout_; + std::string server_name_; + uint16_t port_; + std::string bindaddr_; + std::vector> task_queue_length_pool_; + + std::chrono::milliseconds tick_interval_; + std::function tick_function_; + + std::tuple* middlewares_; + + typename Adaptor::context* adaptor_ctx_; + }; +} // namespace crow + +#include +#include +#include +#include +#include + +namespace crow +{ + namespace mustache + { + using context = json::wvalue; + + template_t load(const std::string& filename); + + class invalid_template_exception : public std::exception + { + public: + invalid_template_exception(const std::string& msg): + msg("crow::mustache error: " + msg) + {} + virtual const char* what() const throw() + { + return msg.c_str(); + } + std::string msg; + }; + + struct rendered_template : returnable + { + rendered_template(): + returnable("text/html") {} + + rendered_template(std::string& body): + returnable("text/html"), body_(std::move(body)) {} + + std::string body_; + + std::string dump() const override + { + return body_; + } + }; + + enum class ActionType + { + Ignore, + Tag, + UnescapeTag, + OpenBlock, + CloseBlock, + ElseBlock, + Partial, + }; + + struct Action + { + int start; + int end; + int pos; + ActionType t; + Action(ActionType t, size_t start, size_t end, size_t pos = 0): + start(static_cast(start)), end(static_cast(end)), pos(static_cast(pos)), t(t) + { + } + }; + + /// A mustache template object. + class template_t + { + public: + template_t(std::string body): + body_(std::move(body)) + { + // {{ {{# {{/ {{^ {{! {{> {{= + parse(); + } + + private: + std::string tag_name(const Action& action) const + { + return body_.substr(action.start, action.end - action.start); + } + auto find_context(const std::string& name, const std::vector& stack, bool shouldUseOnlyFirstStackValue = false) const -> std::pair + { + if (name == ".") + { + return {true, *stack.back()}; + } + static json::wvalue empty_str; + empty_str = ""; + + int dotPosition = name.find("."); + if (dotPosition == static_cast(name.npos)) + { + for (auto it = stack.rbegin(); it != stack.rend(); ++it) + { + if ((*it)->t() == json::type::Object) + { + if ((*it)->count(name)) + return {true, (**it)[name]}; + } + } + } + else + { + std::vector dotPositions; + dotPositions.push_back(-1); + while (dotPosition != static_cast(name.npos)) + { + dotPositions.push_back(dotPosition); + dotPosition = name.find(".", dotPosition + 1); + } + dotPositions.push_back(name.size()); + std::vector names; + names.reserve(dotPositions.size() - 1); + for (int i = 1; i < static_cast(dotPositions.size()); i++) + names.emplace_back(name.substr(dotPositions[i - 1] + 1, dotPositions[i] - dotPositions[i - 1] - 1)); + + for (auto it = stack.rbegin(); it != stack.rend(); ++it) + { + const context* view = *it; + bool found = true; + for (auto jt = names.begin(); jt != names.end(); ++jt) + { + if (view->t() == json::type::Object && + view->count(*jt)) + { + view = &(*view)[*jt]; + } + else + { + if (shouldUseOnlyFirstStackValue) + { + return {false, empty_str}; + } + found = false; + break; + } + } + if (found) + return {true, *view}; + } + } + + return {false, empty_str}; + } + + void escape(const std::string& in, std::string& out) const + { + out.reserve(out.size() + in.size()); + for (auto it = in.begin(); it != in.end(); ++it) + { + switch (*it) + { + case '&': out += "&"; break; + case '<': out += "<"; break; + case '>': out += ">"; break; + case '"': out += """; break; + case '\'': out += "'"; break; + case '/': out += "/"; break; + case '`': out += "`"; break; + case '=': out += "="; break; + default: out += *it; break; + } + } + } + + bool isTagInsideObjectBlock(const int& current, const std::vector& stack) const + { + int openedBlock = 0; + for (int i = current; i > 0; --i) + { + auto& action = actions_[i - 1]; + + if (action.t == ActionType::OpenBlock) + { + if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object) + { + return true; + } + --openedBlock; + } + else if (action.t == ActionType::CloseBlock) + { + ++openedBlock; + } + } + + return false; + } + + void render_internal(int actionBegin, int actionEnd, std::vector& stack, std::string& out, int indent) const + { + int current = actionBegin; + + if (indent) + out.insert(out.size(), indent, ' '); + + while (current < actionEnd) + { + auto& fragment = fragments_[current]; + auto& action = actions_[current]; + render_fragment(fragment, indent, out); + switch (action.t) + { + case ActionType::Ignore: + // do nothing + break; + case ActionType::Partial: + { + std::string partial_name = tag_name(action); + auto partial_templ = load(partial_name); + int partial_indent = action.pos; + partial_templ.render_internal(0, partial_templ.fragments_.size() - 1, stack, out, partial_indent ? indent + partial_indent : 0); + } + break; + case ActionType::UnescapeTag: + case ActionType::Tag: + { + bool shouldUseOnlyFirstStackValue = false; + if (isTagInsideObjectBlock(current, stack)) + { + shouldUseOnlyFirstStackValue = true; + } + auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue); + auto& ctx = optional_ctx.second; + switch (ctx.t()) + { + case json::type::False: + case json::type::True: + case json::type::Number: + out += ctx.dump(); + break; + case json::type::String: + if (action.t == ActionType::Tag) + escape(ctx.s, out); + else + out += ctx.s; + break; + case json::type::Function: + { + std::string execute_result = ctx.execute(); + while (execute_result.find("{{") != std::string::npos) + { + template_t result_plug(execute_result); + execute_result = result_plug.render_string(*(stack[0])); + } + + if (action.t == ActionType::Tag) + escape(execute_result, out); + else + out += execute_result; + } + break; + default: + throw std::runtime_error("not implemented tag type" + utility::lexical_cast(static_cast(ctx.t()))); + } + } + break; + case ActionType::ElseBlock: + { + static context nullContext; + auto optional_ctx = find_context(tag_name(action), stack); + if (!optional_ctx.first) + { + stack.emplace_back(&nullContext); + break; + } + + auto& ctx = optional_ctx.second; + switch (ctx.t()) + { + case json::type::List: + if (ctx.l && !ctx.l->empty()) + current = action.pos; + else + stack.emplace_back(&nullContext); + break; + case json::type::False: + case json::type::Null: + stack.emplace_back(&nullContext); + break; + default: + current = action.pos; + break; + } + break; + } + case ActionType::OpenBlock: + { + auto optional_ctx = find_context(tag_name(action), stack); + if (!optional_ctx.first) + { + current = action.pos; + break; + } + + auto& ctx = optional_ctx.second; + switch (ctx.t()) + { + case json::type::List: + if (ctx.l) + for (auto it = ctx.l->begin(); it != ctx.l->end(); ++it) + { + stack.push_back(&*it); + render_internal(current + 1, action.pos, stack, out, indent); + stack.pop_back(); + } + current = action.pos; + break; + case json::type::Number: + case json::type::String: + case json::type::Object: + case json::type::True: + stack.push_back(&ctx); + break; + case json::type::False: + case json::type::Null: + current = action.pos; + break; + default: + throw std::runtime_error("{{#: not implemented context type: " + utility::lexical_cast(static_cast(ctx.t()))); + break; + } + break; + } + case ActionType::CloseBlock: + stack.pop_back(); + break; + default: + throw std::runtime_error("not implemented " + utility::lexical_cast(static_cast(action.t))); + } + current++; + } + auto& fragment = fragments_[actionEnd]; + render_fragment(fragment, indent, out); + } + void render_fragment(const std::pair fragment, int indent, std::string& out) const + { + if (indent) + { + for (int i = fragment.first; i < fragment.second; i++) + { + out += body_[i]; + if (body_[i] == '\n' && i + 1 != static_cast(body_.size())) + out.insert(out.size(), indent, ' '); + } + } + else + out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first); + } + + public: + /// Output a returnable template from this mustache template + rendered_template render() const + { + context empty_ctx; + std::vector stack; + stack.emplace_back(&empty_ctx); + + std::string ret; + render_internal(0, fragments_.size() - 1, stack, ret, 0); + return rendered_template(ret); + } + + /// Apply the values from the context provided and output a returnable template from this mustache template + rendered_template render(const context& ctx) const + { + std::vector stack; + stack.emplace_back(&ctx); + + std::string ret; + render_internal(0, fragments_.size() - 1, stack, ret, 0); + return rendered_template(ret); + } + + /// Apply the values from the context provided and output a returnable template from this mustache template + rendered_template render(const context&& ctx) const + { + return render(ctx); + } + + /// Output a returnable template from this mustache template + std::string render_string() const + { + context empty_ctx; + std::vector stack; + stack.emplace_back(&empty_ctx); + + std::string ret; + render_internal(0, fragments_.size() - 1, stack, ret, 0); + return ret; + } + + /// Apply the values from the context provided and output a returnable template from this mustache template + std::string render_string(const context& ctx) const + { + std::vector stack; + stack.emplace_back(&ctx); + + std::string ret; + render_internal(0, fragments_.size() - 1, stack, ret, 0); + return ret; + } + + private: + void parse() + { + std::string tag_open = "{{"; + std::string tag_close = "}}"; + + std::vector blockPositions; + + size_t current = 0; + while (1) + { + size_t idx = body_.find(tag_open, current); + if (idx == body_.npos) + { + fragments_.emplace_back(static_cast(current), static_cast(body_.size())); + actions_.emplace_back(ActionType::Ignore, 0, 0); + break; + } + fragments_.emplace_back(static_cast(current), static_cast(idx)); + + idx += tag_open.size(); + size_t endIdx = body_.find(tag_close, idx); + if (endIdx == idx) + { + throw invalid_template_exception("empty tag is not allowed"); + } + if (endIdx == body_.npos) + { + // error, no matching tag + throw invalid_template_exception("not matched opening tag"); + } + current = endIdx + tag_close.size(); + switch (body_[idx]) + { + case '#': + idx++; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + blockPositions.emplace_back(static_cast(actions_.size())); + actions_.emplace_back(ActionType::OpenBlock, idx, endIdx); + break; + case '/': + idx++; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + { + auto& matched = actions_[blockPositions.back()]; + if (body_.compare(idx, endIdx - idx, + body_, matched.start, matched.end - matched.start) != 0) + { + throw invalid_template_exception("not matched {{# {{/ pair: " + + body_.substr(matched.start, matched.end - matched.start) + ", " + + body_.substr(idx, endIdx - idx)); + } + matched.pos = actions_.size(); + } + actions_.emplace_back(ActionType::CloseBlock, idx, endIdx, blockPositions.back()); + blockPositions.pop_back(); + break; + case '^': + idx++; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + blockPositions.emplace_back(static_cast(actions_.size())); + actions_.emplace_back(ActionType::ElseBlock, idx, endIdx); + break; + case '!': + // do nothing action + actions_.emplace_back(ActionType::Ignore, idx + 1, endIdx); + break; + case '>': // partial + idx++; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + actions_.emplace_back(ActionType::Partial, idx, endIdx); + break; + case '{': + if (tag_open != "{{" || tag_close != "}}") + throw invalid_template_exception("cannot use triple mustache when delimiter changed"); + + idx++; + if (body_[endIdx + 2] != '}') + { + throw invalid_template_exception("{{{: }}} not matched"); + } + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx); + current++; + break; + case '&': + idx++; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx); + break; + case '=': + // tag itself is no-op + idx++; + actions_.emplace_back(ActionType::Ignore, idx, endIdx); + endIdx--; + if (body_[endIdx] != '=') + throw invalid_template_exception("{{=: not matching = tag: " + body_.substr(idx, endIdx - idx)); + endIdx--; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx] == ' ') + endIdx--; + endIdx++; + { + bool succeeded = false; + for (size_t i = idx; i < endIdx; i++) + { + if (body_[i] == ' ') + { + tag_open = body_.substr(idx, i - idx); + while (body_[i] == ' ') + i++; + tag_close = body_.substr(i, endIdx - i); + if (tag_open.empty()) + throw invalid_template_exception("{{=: empty open tag"); + if (tag_close.empty()) + throw invalid_template_exception("{{=: empty close tag"); + + if (tag_close.find(" ") != tag_close.npos) + throw invalid_template_exception("{{=: invalid open/close tag: " + tag_open + " " + tag_close); + succeeded = true; + break; + } + } + if (!succeeded) + throw invalid_template_exception("{{=: cannot find space between new open/close tags"); + } + break; + default: + // normal tag case; + while (body_[idx] == ' ') + idx++; + while (body_[endIdx - 1] == ' ') + endIdx--; + actions_.emplace_back(ActionType::Tag, idx, endIdx); + break; + } + } + + // removing standalones + for (int i = actions_.size() - 2; i >= 0; i--) + { + if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag) + continue; + auto& fragment_before = fragments_[i]; + auto& fragment_after = fragments_[i + 1]; + bool is_last_action = i == static_cast(actions_.size()) - 2; + bool all_space_before = true; + int j, k; + for (j = fragment_before.second - 1; j >= fragment_before.first; j--) + { + if (body_[j] != ' ') + { + all_space_before = false; + break; + } + } + if (all_space_before && i > 0) + continue; + if (!all_space_before && body_[j] != '\n') + continue; + bool all_space_after = true; + for (k = fragment_after.first; k < static_cast(body_.size()) && k < fragment_after.second; k++) + { + if (body_[k] != ' ') + { + all_space_after = false; + break; + } + } + if (all_space_after && !is_last_action) + continue; + if (!all_space_after && + !( + body_[k] == '\n' || + (body_[k] == '\r' && + k + 1 < static_cast(body_.size()) && + body_[k + 1] == '\n'))) + continue; + if (actions_[i].t == ActionType::Partial) + { + actions_[i].pos = fragment_before.second - j - 1; + } + fragment_before.second = j + 1; + if (!all_space_after) + { + if (body_[k] == '\n') + k++; + else + k += 2; + fragment_after.first = k; + } + } + } + + std::vector> fragments_; + std::vector actions_; + std::string body_; + }; + + inline template_t compile(const std::string& body) + { + return template_t(body); + } + namespace detail + { + inline std::string& get_template_base_directory_ref() + { + static std::string template_base_directory = "templates"; + return template_base_directory; + } + + /// A base directory not related to any blueprint + inline std::string& get_global_template_base_directory_ref() + { + static std::string template_base_directory = "templates"; + return template_base_directory; + } + } // namespace detail + + inline std::string default_loader(const std::string& filename) + { + std::string path = detail::get_template_base_directory_ref(); + std::ifstream inf(utility::join_path(path, filename)); + if (!inf) + { + CROW_LOG_WARNING << "Template \"" << filename << "\" not found."; + return {}; + } + return {std::istreambuf_iterator(inf), std::istreambuf_iterator()}; + } + + namespace detail + { + inline std::function& get_loader_ref() + { + static std::function loader = default_loader; + return loader; + } + } // namespace detail + + inline void set_base(const std::string& path) + { + auto& base = detail::get_template_base_directory_ref(); + base = path; + if (base.back() != '\\' && + base.back() != '/') + { + base += '/'; + } + } + + inline void set_global_base(const std::string& path) + { + auto& base = detail::get_global_template_base_directory_ref(); + base = path; + if (base.back() != '\\' && + base.back() != '/') + { + base += '/'; + } + } + + inline void set_loader(std::function loader) + { + detail::get_loader_ref() = std::move(loader); + } + + inline std::string load_text(const std::string& filename) + { + std::string filename_sanitized(filename); + utility::sanitize_filename(filename_sanitized); + return detail::get_loader_ref()(filename_sanitized); + } + + inline std::string load_text_unsafe(const std::string& filename) + { + return detail::get_loader_ref()(filename); + } + + inline template_t load(const std::string& filename) + { + std::string filename_sanitized(filename); + utility::sanitize_filename(filename_sanitized); + return compile(detail::get_loader_ref()(filename_sanitized)); + } + + inline template_t load_unsafe(const std::string& filename) + { + return compile(detail::get_loader_ref()(filename)); + } + } // namespace mustache +} // namespace crow + + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace crow // NOTE: Already documented in "crow/app.h" +{ + + constexpr const uint16_t INVALID_BP_ID{((uint16_t)-1)}; + + namespace detail + { + /// Typesafe wrapper for storing lists of middleware as their indices in the App + struct middleware_indices + { + template + void push() + {} + + template + void push() + { + using MwContainer = typename App::mw_container_t; + static_assert(black_magic::has_type::value, "Middleware must be present in app"); + static_assert(std::is_base_of::value, "Middleware must extend ILocalMiddleware"); + int idx = black_magic::tuple_index::value; + indices_.push_back(idx); + push(); + } + + void merge_front(const detail::middleware_indices& other) + { + indices_.insert(indices_.begin(), other.indices_.cbegin(), other.indices_.cend()); + } + + void merge_back(const detail::middleware_indices& other) + { + indices_.insert(indices_.end(), other.indices_.cbegin(), other.indices_.cend()); + } + + void pop_back(const detail::middleware_indices& other) + { + indices_.resize(indices_.size() - other.indices_.size()); + } + + bool empty() const + { + return indices_.empty(); + } + + // Sorts indices and filters out duplicates to allow fast lookups with traversal + void pack() + { + std::sort(indices_.begin(), indices_.end()); + indices_.erase(std::unique(indices_.begin(), indices_.end()), indices_.end()); + } + + const std::vector& indices() + { + return indices_; + } + + private: + std::vector indices_; + }; + } // namespace detail + + /// A base class for all rules. + + /// + /// Used to provide a common interface for code dealing with different types of rules.
+ /// A Rule provides a URL, allowed HTTP methods, and handlers. + class BaseRule + { + public: + BaseRule(std::string rule): + rule_(std::move(rule)) + {} + + virtual ~BaseRule() + {} + + virtual void validate() = 0; + + void set_added() { + added_ = true; + } + + bool is_added() { + return added_; + } + + std::unique_ptr upgrade() + { + if (rule_to_upgrade_) + return std::move(rule_to_upgrade_); + return {}; + } + + virtual void handle(request&, response&, const routing_params&) = 0; + virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) + { + res = response(404); + res.end(); + } +#ifdef CROW_ENABLE_SSL + virtual void handle_upgrade(const request&, response& res, SSLAdaptor&&) + { + res = response(404); + res.end(); + } +#endif + + uint32_t get_methods() + { + return methods_; + } + + template + void foreach_method(F f) + { + for (uint32_t method = 0, method_bit = 1; method < static_cast(HTTPMethod::InternalMethodCount); method++, method_bit <<= 1) + { + if (methods_ & method_bit) + f(method); + } + } + + std::string custom_templates_base; + + const std::string& rule() { return rule_; } + + protected: + uint32_t methods_{1 << static_cast(HTTPMethod::Get)}; + + std::string rule_; + std::string name_; + bool added_{false}; + + std::unique_ptr rule_to_upgrade_; + + detail::middleware_indices mw_indices_; + + friend class Router; + friend class Blueprint; + template + friend struct RuleParameterTraits; + }; + + + namespace detail + { + namespace routing_handler_call_helper + { + template + struct call_pair + { + using type = T; + static const int pos = Pos; + }; + + template + struct call_params + { + H1& handler; + const routing_params& params; + request& req; + response& res; + }; + + template + struct call + {}; + + template + struct call, black_magic::S> + { + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back>; + call, pushed>()(cparams); + } + }; + + template + struct call, black_magic::S> + { + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back>; + call, pushed>()(cparams); + } + }; + + template + struct call, black_magic::S> + { + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back>; + call, pushed>()(cparams); + } + }; + + template + struct call, black_magic::S> + { + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back>; + call, pushed>()(cparams); + } + }; + + template + struct call, black_magic::S> + { + void operator()(F cparams) + { + cparams.handler( + cparams.req, + cparams.res, + cparams.params.template get(Args1::pos)...); + } + }; + + template + struct Wrapped + { + template + void set_(Func f, typename std::enable_if>::type, const request&>::value, int>::type = 0) + { + handler_ = ( +#ifdef CROW_CAN_USE_CPP14 + [f = std::move(f)] +#else + [f] +#endif + (const request&, response& res, Args... args) { + res = response(f(args...)); + res.end(); + }); + } + + template + struct req_handler_wrapper + { + req_handler_wrapper(Func f): + f(std::move(f)) + { + } + + void operator()(const request& req, response& res, Args... args) + { + res = response(f(req, args...)); + res.end(); + } + + Func f; + }; + + template + void set_(Func f, typename std::enable_if< + std::is_same>::type, const request&>::value && + !std::is_same>::type, response&>::value, + int>::type = 0) + { + handler_ = req_handler_wrapper(std::move(f)); + /*handler_ = ( + [f = std::move(f)] + (const request& req, response& res, Args... args){ + res = response(f(req, args...)); + res.end(); + });*/ + } + + template + void set_(Func f, typename std::enable_if< + std::is_same>::type, const request&>::value && + std::is_same>::type, response&>::value, + int>::type = 0) + { + handler_ = std::move(f); + } + + template + struct handler_type_helper + { + using type = std::function; + using args_type = black_magic::S...>; + }; + + template + struct handler_type_helper + { + using type = std::function; + using args_type = black_magic::S...>; + }; + + template + struct handler_type_helper + { + using type = std::function; + using args_type = black_magic::S...>; + }; + + typename handler_type_helper::type handler_; + + void operator()(request& req, response& res, const routing_params& params) + { + detail::routing_handler_call_helper::call< + detail::routing_handler_call_helper::call_params< + decltype(handler_)>, + 0, 0, 0, 0, + typename handler_type_helper::args_type, + black_magic::S<>>()( + detail::routing_handler_call_helper::call_params< + decltype(handler_)>{handler_, params, req, res}); + } + }; + + } // namespace routing_handler_call_helper + } // namespace detail + + + class CatchallRule + { + public: + /// @cond SKIP + CatchallRule() {} + + template + typename std::enable_if>::value, void>::type + operator()(Func&& f) + { + static_assert(!std::is_same::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + handler_ = ( +#ifdef CROW_CAN_USE_CPP14 + [f = std::move(f)] +#else + [f] +#endif + (const request&, response& res) { + res = response(f()); + res.end(); + }); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + black_magic::CallHelper>::value, + void>::type + operator()(Func&& f) + { + static_assert(!std::is_same()))>::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + handler_ = ( +#ifdef CROW_CAN_USE_CPP14 + [f = std::move(f)] +#else + [f] +#endif + (const crow::request& req, crow::response& res) { + res = response(f(req)); + res.end(); + }); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + black_magic::CallHelper>::value, + void>::type + operator()(Func&& f) + { + static_assert(std::is_same()))>::value, + "Handler function with response argument should have void return type"); + handler_ = ( +#ifdef CROW_CAN_USE_CPP14 + [f = std::move(f)] +#else + [f] +#endif + (const crow::request&, crow::response& res) { + f(res); + }); + } + + template + typename std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value && + !black_magic::CallHelper>::value, + void>::type + operator()(Func&& f) + { + static_assert(std::is_same(), std::declval()))>::value, + "Handler function with response argument should have void return type"); + + handler_ = std::move(f); + } + /// @endcond + bool has_handler() + { + return (handler_ != nullptr); + } + + protected: + friend class Router; + + private: + std::function handler_; + }; + + + /// A rule dealing with websockets. + + /// + /// Provides the interface for the user to put in the necessary handlers for a websocket to work. + template + class WebSocketRule : public BaseRule + { + using self_t = WebSocketRule; + + public: + WebSocketRule(std::string rule, App* app): + BaseRule(std::move(rule)), + app_(app), + max_payload_(UINT64_MAX) + {} + + void validate() override + {} + + void handle(request&, response& res, const routing_params&) override + { + res = response(404); + res.end(); + } + + void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override + { + max_payload_ = max_payload_override_ ? max_payload_ : app_->websocket_max_payload(); + new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + } +#ifdef CROW_ENABLE_SSL + void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override + { + new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + } +#endif + + /// Override the global payload limit for this single WebSocket rule + self_t& max_payload(uint64_t max_payload) + { + max_payload_ = max_payload; + max_payload_override_ = true; + return *this; + } + + template + self_t& onopen(Func f) + { + open_handler_ = f; + return *this; + } + + template + self_t& onmessage(Func f) + { + message_handler_ = f; + return *this; + } + + template + self_t& onclose(Func f) + { + close_handler_ = f; + return *this; + } + + template + self_t& onerror(Func f) + { + error_handler_ = f; + return *this; + } + + template + self_t& onaccept(Func f) + { + accept_handler_ = f; + return *this; + } + + protected: + App* app_; + std::function open_handler_; + std::function message_handler_; + std::function close_handler_; + std::function error_handler_; + std::function accept_handler_; + uint64_t max_payload_; + bool max_payload_override_ = false; + }; + + /// Allows the user to assign parameters using functions. + + /// + /// `rule.name("name").methods(HTTPMethod::POST)` + template + struct RuleParameterTraits + { + using self_t = T; + + template + WebSocketRule& websocket(App* app) + { + auto p = new WebSocketRule(static_cast(this)->rule_, app); + static_cast(this)->rule_to_upgrade_.reset(p); + return *p; + } + + self_t& name(std::string name) noexcept + { + static_cast(this)->name_ = std::move(name); + return static_cast(*this); + } + + self_t& methods(HTTPMethod method) + { + static_cast(this)->methods_ = 1 << static_cast(method); + return static_cast(*this); + } + + template + self_t& methods(HTTPMethod method, MethodArgs... args_method) + { + methods(args_method...); + static_cast(this)->methods_ |= 1 << static_cast(method); + return static_cast(*this); + } + + /// Enable local middleware for this handler + template + self_t& middlewares() + { + static_cast(this)->mw_indices_.template push(); + return static_cast(*this); + } + }; + + /// A rule that can change its parameters during runtime. + class DynamicRule : public BaseRule, public RuleParameterTraits + { + public: + DynamicRule(std::string rule): + BaseRule(std::move(rule)) + {} + + void validate() override + { + if (!erased_handler_) + { + throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); + } + } + + void handle(request& req, response& res, const routing_params& params) override + { + if (!custom_templates_base.empty()) + mustache::set_base(custom_templates_base); + else if (mustache::detail::get_template_base_directory_ref() != "templates") + mustache::set_base("templates"); + erased_handler_(req, res, params); + } + + template + void operator()(Func f) + { +#ifdef CROW_MSVC_WORKAROUND + using function_t = utility::function_traits; +#else + using function_t = utility::function_traits; +#endif + erased_handler_ = wrap(std::move(f), black_magic::gen_seq()); + } + + // enable_if Arg1 == request && Arg2 == response + // enable_if Arg1 == request && Arg2 != resposne + // enable_if Arg1 != request +#ifdef CROW_MSVC_WORKAROUND + template +#else + template +#endif + std::function + wrap(Func f, black_magic::seq) + { +#ifdef CROW_MSVC_WORKAROUND + using function_t = utility::function_traits; +#else + using function_t = utility::function_traits; +#endif + if (!black_magic::is_parameter_tag_compatible( + black_magic::get_parameter_tag_runtime(rule_.c_str()), + black_magic::compute_parameter_tag_from_args_list< + typename function_t::template arg...>::value)) + { + throw std::runtime_error("route_dynamic: Handler type is mismatched with URL parameters: " + rule_); + } + auto ret = detail::routing_handler_call_helper::Wrapped...>(); + ret.template set_< + typename function_t::template arg...>(std::move(f)); + return ret; + } + + template + void operator()(std::string name, Func&& f) + { + name_ = std::move(name); + (*this).template operator()(std::forward(f)); + } + + private: + std::function erased_handler_; + }; + + /// Default rule created when CROW_ROUTE is called. + template + class TaggedRule : public BaseRule, public RuleParameterTraits> + { + public: + using self_t = TaggedRule; + + TaggedRule(std::string rule): + BaseRule(std::move(rule)) + {} + + void validate() override + { + if (rule_.at(0) != '/') + throw std::runtime_error("Internal error: Routes must start with a '/'"); + + if (!handler_) + { + throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); + } + } + + template + void operator()(Func&& f) + { + handler_ = ( +#ifdef CROW_CAN_USE_CPP14 + [f = std::move(f)] +#else + [f] +#endif + (crow::request& req, crow::response& res, Args... args) { + detail::wrapped_handler_call(req, res, f, std::forward(args)...); + }); + } + + template + void operator()(std::string name, Func&& f) + { + name_ = std::move(name); + (*this).template operator()(std::forward(f)); + } + + void handle(request& req, response& res, const routing_params& params) override + { + if (!custom_templates_base.empty()) + mustache::set_base(custom_templates_base); + else if (mustache::detail::get_template_base_directory_ref() != mustache::detail::get_global_template_base_directory_ref()) + mustache::set_base(mustache::detail::get_global_template_base_directory_ref()); + + detail::routing_handler_call_helper::call< + detail::routing_handler_call_helper::call_params, + 0, 0, 0, 0, + black_magic::S, + black_magic::S<>>()( + detail::routing_handler_call_helper::call_params{handler_, params, req, res}); + } + + private: + std::function handler_; + }; + + const int RULE_SPECIAL_REDIRECT_SLASH = 1; + + + /// A search tree. + class Trie + { + public: + struct Node + { + uint16_t rule_index{}; + // Assign the index to the maximum 32 unsigned integer value by default so that any other number (specifically 0) is a valid BP id. + uint16_t blueprint_index{INVALID_BP_ID}; + std::string key; + ParamType param = ParamType::MAX; // MAX = No param. + std::vector children; + + bool IsSimpleNode() const + { + return !rule_index && + blueprint_index == INVALID_BP_ID && + children.size() < 2 && + param == ParamType::MAX && + std::all_of(std::begin(children), std::end(children), [](const Node& x) { + return x.param == ParamType::MAX; + }); + } + + Node& add_child_node() + { + children.emplace_back(); + return children.back(); + } + }; + + + Trie() + {} + + /// Check whether or not the trie is empty. + bool is_empty() + { + return head_.children.empty(); + } + + void optimize() + { + for (auto& child : head_.children) + { + optimizeNode(child); + } + } + + + private: + void optimizeNode(Node& node) + { + if (node.children.empty()) + return; + if (node.IsSimpleNode()) + { + auto children_temp = std::move(node.children); + auto& child_temp = children_temp[0]; + node.key += child_temp.key; + node.rule_index = child_temp.rule_index; + node.blueprint_index = child_temp.blueprint_index; + node.children = std::move(child_temp.children); + optimizeNode(node); + } + else + { + for (auto& child : node.children) + { + optimizeNode(child); + } + } + } + + void debug_node_print(const Node& node, int level) + { + if (node.param != ParamType::MAX) + { + switch (node.param) + { + case ParamType::INT: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + case ParamType::UINT: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + case ParamType::DOUBLE: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + case ParamType::STRING: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + case ParamType::PATH: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + default: + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " + << ""; + break; + } + } + else + CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " << node.key; + + for (const auto& child : node.children) + { + debug_node_print(child, level + 1); + } + } + + public: + void debug_print() + { + CROW_LOG_DEBUG << "└➙ ROOT"; + for (const auto& child : head_.children) + debug_node_print(child, 1); + } + + void validate() + { + if (!head_.IsSimpleNode()) + throw std::runtime_error("Internal error: Trie header should be simple!"); + optimize(); + } + + //Rule_index, Blueprint_index, routing_params + routing_handle_result find(const std::string& req_url, const Node& node, unsigned pos = 0, routing_params* params = nullptr, std::vector* blueprints = nullptr) const + { + //start params as an empty struct + routing_params empty; + if (params == nullptr) + params = ∅ + //same for blueprint vector + std::vector MT; + if (blueprints == nullptr) + blueprints = &MT; + + uint16_t found{}; //The rule index to be found + std::vector found_BP; //The Blueprint indices to be found + routing_params match_params; //supposedly the final matched parameters + + auto update_found = [&found, &found_BP, &match_params](routing_handle_result& ret) { + found_BP = std::move(ret.blueprint_indices); + if (ret.rule_index && (!found || found > ret.rule_index)) + { + found = ret.rule_index; + match_params = std::move(ret.r_params); + } + }; + + //if the function was called on a node at the end of the string (the last recursion), return the nodes rule index, and whatever params were passed to the function + if (pos == req_url.size()) + { + found_BP = std::move(*blueprints); + return routing_handle_result{node.rule_index, *blueprints, *params}; + } + + bool found_fragment = false; + + for (const auto& child : node.children) + { + if (child.param != ParamType::MAX) + { + if (child.param == ParamType::INT) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+' || c == '-') + { + char* eptr; + errno = 0; + long long int value = strtoll(req_url.data() + pos, &eptr, 10); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + found_fragment = true; + params->int_params.push_back(value); + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); + update_found(ret); + params->int_params.pop_back(); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + } + + else if (child.param == ParamType::UINT) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+') + { + char* eptr; + errno = 0; + unsigned long long int value = strtoull(req_url.data() + pos, &eptr, 10); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + found_fragment = true; + params->uint_params.push_back(value); + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); + update_found(ret); + params->uint_params.pop_back(); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + } + + else if (child.param == ParamType::DOUBLE) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') + { + char* eptr; + errno = 0; + double value = strtod(req_url.data() + pos, &eptr); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + found_fragment = true; + params->double_params.push_back(value); + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); + update_found(ret); + params->double_params.pop_back(); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + } + + else if (child.param == ParamType::STRING) + { + size_t epos = pos; + for (; epos < req_url.size(); epos++) + { + if (req_url[epos] == '/') + break; + } + + if (epos != pos) + { + found_fragment = true; + params->string_params.push_back(req_url.substr(pos, epos - pos)); + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, epos, params, blueprints); + update_found(ret); + params->string_params.pop_back(); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + + else if (child.param == ParamType::PATH) + { + size_t epos = req_url.size(); + + if (epos != pos) + { + found_fragment = true; + params->string_params.push_back(req_url.substr(pos, epos - pos)); + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, epos, params, blueprints); + update_found(ret); + params->string_params.pop_back(); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + } + + else + { + const std::string& fragment = child.key; + if (req_url.compare(pos, fragment.size(), fragment) == 0) + { + found_fragment = true; + if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); + auto ret = find(req_url, child, pos + fragment.size(), params, blueprints); + update_found(ret); + if (!blueprints->empty()) blueprints->pop_back(); + } + } + } + + if (!found_fragment) + found_BP = std::move(*blueprints); + + return routing_handle_result{found, found_BP, match_params}; //Called after all the recursions have been done + } + + routing_handle_result find(const std::string& req_url) const + { + return find(req_url, head_); + } + + //This functions assumes any blueprint info passed is valid + void add(const std::string& url, uint16_t rule_index, unsigned bp_prefix_length = 0, uint16_t blueprint_index = INVALID_BP_ID) + { + auto idx = &head_; + + bool has_blueprint = bp_prefix_length != 0 && blueprint_index != INVALID_BP_ID; + + for (unsigned i = 0; i < url.size(); i++) + { + char c = url[i]; + if (c == '<') + { + static struct ParamTraits + { + ParamType type; + std::string name; + } paramTraits[] = + { + {ParamType::INT, ""}, + {ParamType::UINT, ""}, + {ParamType::DOUBLE, ""}, + {ParamType::DOUBLE, ""}, + {ParamType::STRING, ""}, + {ParamType::STRING, ""}, + {ParamType::PATH, ""}, + }; + + for (const auto& x : paramTraits) + { + if (url.compare(i, x.name.size(), x.name) == 0) + { + bool found = false; + for (auto& child : idx->children) + { + if (child.param == x.type) + { + idx = &child; + i += x.name.size(); + found = true; + break; + } + } + if (found) + break; + + auto new_node_idx = &idx->add_child_node(); + new_node_idx->param = x.type; + idx = new_node_idx; + i += x.name.size(); + break; + } + } + + i--; + } + else + { + //This part assumes the tree is unoptimized (every node has a max 1 character key) + bool piece_found = false; + for (auto& child : idx->children) + { + if (child.key[0] == c) + { + idx = &child; + piece_found = true; + break; + } + } + if (!piece_found) + { + auto new_node_idx = &idx->add_child_node(); + new_node_idx->key = c; + //The assumption here is that you'd only need to add a blueprint index if the tree didn't have the BP prefix. + if (has_blueprint && i == bp_prefix_length) + new_node_idx->blueprint_index = blueprint_index; + idx = new_node_idx; + } + } + } + + //check if the last node already has a value (exact url already in Trie) + if (idx->rule_index) + throw std::runtime_error("handler already exists for " + url); + idx->rule_index = rule_index; + } + + private: + Node head_; + }; + + /// A blueprint can be considered a smaller section of a Crow app, specifically where the router is conecerned. + + /// + /// You can use blueprints to assign a common prefix to rules' prefix, set custom static and template folders, and set a custom catchall route. + /// You can also assign nest blueprints for maximum Compartmentalization. + class Blueprint + { + public: + Blueprint(const std::string& prefix): + prefix_(prefix){}; + + Blueprint(const std::string& prefix, const std::string& static_dir): + prefix_(prefix), static_dir_(static_dir){}; + + Blueprint(const std::string& prefix, const std::string& static_dir, const std::string& templates_dir): + prefix_(prefix), static_dir_(static_dir), templates_dir_(templates_dir){}; + + /* + Blueprint(Blueprint& other) + { + prefix_ = std::move(other.prefix_); + all_rules_ = std::move(other.all_rules_); + } + + Blueprint(const Blueprint& other) + { + prefix_ = other.prefix_; + all_rules_ = other.all_rules_; + } +*/ + Blueprint(Blueprint&& value) + { + *this = std::move(value); + } + + Blueprint& operator=(const Blueprint& value) = delete; + + Blueprint& operator=(Blueprint&& value) noexcept + { + prefix_ = std::move(value.prefix_); + static_dir_ = std::move(value.static_dir_); + templates_dir_ = std::move(value.templates_dir_); + all_rules_ = std::move(value.all_rules_); + catchall_rule_ = std::move(value.catchall_rule_); + blueprints_ = std::move(value.blueprints_); + mw_indices_ = std::move(value.mw_indices_); + return *this; + } + + bool operator==(const Blueprint& value) + { + return value.prefix() == prefix_; + } + + bool operator!=(const Blueprint& value) + { + return value.prefix() != prefix_; + } + + std::string prefix() const + { + return prefix_; + } + + std::string static_dir() const + { + return static_dir_; + } + + void set_added() { + added_ = true; + } + + bool is_added() { + return added_; + } + + DynamicRule& new_rule_dynamic(const std::string& rule) + { + std::string new_rule = '/' + prefix_ + rule; + auto ruleObject = new DynamicRule(std::move(new_rule)); + ruleObject->custom_templates_base = templates_dir_; + all_rules_.emplace_back(ruleObject); + + return *ruleObject; + } + + template + typename black_magic::arguments::type::template rebind& new_rule_tagged(const std::string& rule) + { + std::string new_rule = '/' + prefix_ + rule; + using RuleT = typename black_magic::arguments::type::template rebind; + + auto ruleObject = new RuleT(std::move(new_rule)); + ruleObject->custom_templates_base = templates_dir_; + all_rules_.emplace_back(ruleObject); + + return *ruleObject; + } + + void register_blueprint(Blueprint& blueprint) + { + if (blueprints_.empty() || std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) + { + apply_blueprint(blueprint); + blueprints_.emplace_back(&blueprint); + } + else + throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in blueprint \"" + prefix_ + '\"'); + } + + + CatchallRule& catchall_rule() + { + return catchall_rule_; + } + + template + void middlewares() + { + mw_indices_.push(); + } + + private: + void apply_blueprint(Blueprint& blueprint) + { + + blueprint.prefix_ = prefix_ + '/' + blueprint.prefix_; + blueprint.static_dir_ = static_dir_ + '/' + blueprint.static_dir_; + blueprint.templates_dir_ = templates_dir_ + '/' + blueprint.templates_dir_; + for (auto& rule : blueprint.all_rules_) + { + std::string new_rule = '/' + prefix_ + rule->rule_; + rule->rule_ = new_rule; + } + for (Blueprint* bp_child : blueprint.blueprints_) + { + Blueprint& bp_ref = *bp_child; + apply_blueprint(bp_ref); + } + } + + std::string prefix_; + std::string static_dir_; + std::string templates_dir_; + std::vector> all_rules_; + CatchallRule catchall_rule_; + std::vector blueprints_; + detail::middleware_indices mw_indices_; + bool added_{false}; + + friend class Router; + }; + + /// Handles matching requests to existing rules and upgrade requests. + class Router + { + public: + Router() + {} + + DynamicRule& new_rule_dynamic(const std::string& rule) + { + auto ruleObject = new DynamicRule(rule); + all_rules_.emplace_back(ruleObject); + + return *ruleObject; + } + + template + typename black_magic::arguments::type::template rebind& new_rule_tagged(const std::string& rule) + { + using RuleT = typename black_magic::arguments::type::template rebind; + + auto ruleObject = new RuleT(rule); + all_rules_.emplace_back(ruleObject); + + return *ruleObject; + } + + CatchallRule& catchall_rule() + { + return catchall_rule_; + } + + void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject) + { + internal_add_rule_object(rule, ruleObject, INVALID_BP_ID, blueprints_); + } + + void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject, const uint16_t& BP_index, std::vector& blueprints) + { + bool has_trailing_slash = false; + std::string rule_without_trailing_slash; + if (rule.size() > 1 && rule.back() == '/') + { + has_trailing_slash = true; + rule_without_trailing_slash = rule; + rule_without_trailing_slash.pop_back(); + } + + ruleObject->mw_indices_.pack(); + + ruleObject->foreach_method([&](int method) { + per_methods_[method].rules.emplace_back(ruleObject); + per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index); + + // directory case: + // request to '/about' url matches '/about/' rule + if (has_trailing_slash) + { + per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index); + } + }); + + ruleObject->set_added(); + } + + void register_blueprint(Blueprint& blueprint) + { + if (std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) + { + blueprints_.emplace_back(&blueprint); + } + else + throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in router"); + } + + void get_recursive_child_methods(Blueprint* blueprint, std::vector& methods) + { + //we only need to deal with children if the blueprint has absolutely no methods (meaning its index won't be added to the trie) + if (blueprint->static_dir_.empty() && blueprint->all_rules_.empty()) + { + for (Blueprint* bp : blueprint->blueprints_) + { + get_recursive_child_methods(bp, methods); + } + } + else if (!blueprint->static_dir_.empty()) + methods.emplace_back(HTTPMethod::Get); + for (auto& rule : blueprint->all_rules_) + { + rule->foreach_method([&methods](unsigned method) { + HTTPMethod method_final = static_cast(method); + if (std::find(methods.begin(), methods.end(), method_final) == methods.end()) + methods.emplace_back(method_final); + }); + } + } + + void validate_bp() { + //Take all the routes from the registered blueprints and add them to `all_rules_` to be processed. + detail::middleware_indices blueprint_mw; + validate_bp(blueprints_, blueprint_mw); + } + + void validate_bp(std::vector blueprints, detail::middleware_indices& current_mw) + { + for (unsigned i = 0; i < blueprints.size(); i++) + { + Blueprint* blueprint = blueprints[i]; + + if (blueprint->is_added()) continue; + + if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty()) + { + std::vector methods; + get_recursive_child_methods(blueprint, methods); + for (HTTPMethod x : methods) + { + int i = static_cast(x); + per_methods_[i].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), i); + } + } + + current_mw.merge_back(blueprint->mw_indices_); + for (auto& rule : blueprint->all_rules_) + { + if (rule && !rule->is_added()) + { + auto upgraded = rule->upgrade(); + if (upgraded) + rule = std::move(upgraded); + rule->validate(); + rule->mw_indices_.merge_front(current_mw); + internal_add_rule_object(rule->rule(), rule.get(), i, blueprints); + } + } + validate_bp(blueprint->blueprints_, current_mw); + current_mw.pop_back(blueprint->mw_indices_); + blueprint->set_added(); + } + } + + void validate() + { + for (auto& rule : all_rules_) + { + if (rule && !rule->is_added()) + { + auto upgraded = rule->upgrade(); + if (upgraded) + rule = std::move(upgraded); + rule->validate(); + internal_add_rule_object(rule->rule(), rule.get()); + } + } + for (auto& per_method : per_methods_) + { + per_method.trie.validate(); + } + } + + // TODO maybe add actual_method + template + void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) + { + if (req.method >= HTTPMethod::InternalMethodCount) + return; + + auto& per_method = per_methods_[static_cast(req.method)]; + auto& rules = per_method.rules; + unsigned rule_index = per_method.trie.find(req.url).rule_index; + + if (!rule_index) + { + for (auto& per_method : per_methods_) + { + if (per_method.trie.find(req.url).rule_index) + { + CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(req.method); + res = response(405); + res.end(); + return; + } + } + + CROW_LOG_INFO << "Cannot match rules " << req.url; + res = response(404); + res.end(); + return; + } + + if (rule_index >= rules.size()) + throw std::runtime_error("Trie internal structure corrupted!"); + + if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) + { + CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; + res = response(301); + + // TODO(ipkn) absolute url building + if (req.get_header_value("Host").empty()) + { + res.add_header("Location", req.url + "/"); + } + else + { + res.add_header("Location", "http://" + req.get_header_value("Host") + req.url + "/"); + } + res.end(); + return; + } + + CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " << static_cast(req.method) << " / " << rules[rule_index]->get_methods(); + + try + { + rules[rule_index]->handle_upgrade(req, res, std::move(adaptor)); + } + catch (...) + { + exception_handler_(res); + res.end(); + return; + } + } + + void get_found_bp(std::vector& bp_i, std::vector& blueprints, std::vector& found_bps, uint16_t index = 0) + { + // This statement makes 3 assertions: + // 1. The index is above 0. + // 2. The index does not lie outside the given blueprint list. + // 3. The next blueprint we're adding has a prefix that starts the same as the already added blueprint + a slash (the rest is irrelevant). + // + // This is done to prevent a blueprint that has a prefix of "bp_prefix2" to be assumed as a child of one that has "bp_prefix". + // + // If any of the assertions is untrue, we delete the last item added, and continue using the blueprint list of the blueprint found before, the topmost being the router's list + auto verify_prefix = [&bp_i, &index, &blueprints, &found_bps]() { + return index > 0 && + bp_i[index] < blueprints.size() && + blueprints[bp_i[index]]->prefix().substr(0, found_bps[index - 1]->prefix().length() + 1).compare(std::string(found_bps[index - 1]->prefix() + '/')) == 0; + }; + if (index < bp_i.size()) + { + + if (verify_prefix()) + { + found_bps.push_back(blueprints[bp_i[index]]); + get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); + } + else + { + if (found_bps.size() < 2) + { + found_bps.clear(); + found_bps.push_back(blueprints_[bp_i[index]]); + } + else + { + found_bps.pop_back(); + Blueprint* last_element = found_bps.back(); + found_bps.push_back(last_element->blueprints_[bp_i[index]]); + } + get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); + } + } + } + + /// Is used to handle errors, you insert the error code, found route, request, and response. and it'll either call the appropriate catchall route (considering the blueprint system) and send you a status string (which is mainly used for debug messages), or just set the response code to the proper error code. + std::string get_error(unsigned short code, routing_handle_result& found, const request& req, response& res) + { + res.code = code; + std::vector bps_found; + get_found_bp(found.blueprint_indices, blueprints_, bps_found); + for (int i = bps_found.size() - 1; i > 0; i--) + { + std::vector bpi = found.blueprint_indices; + if (bps_found[i]->catchall_rule().has_handler()) + { + try + { + bps_found[i]->catchall_rule().handler_(req, res); + } + catch (...) + { + exception_handler_(res); + } +#ifdef CROW_ENABLE_DEBUG + return std::string("Redirected to Blueprint \"" + bps_found[i]->prefix() + "\" Catchall rule"); +#else + return std::string(); +#endif + } + } + if (catchall_rule_.has_handler()) + { + try + { + catchall_rule_.handler_(req, res); + } + catch (...) + { + exception_handler_(res); + } +#ifdef CROW_ENABLE_DEBUG + return std::string("Redirected to global Catchall rule"); +#else + return std::string(); +#endif + } + return std::string(); + } + + std::unique_ptr handle_initial(request& req, response& res) + { + HTTPMethod method_actual = req.method; + + std::unique_ptr found{ + new routing_handle_result( + 0, + std::vector(), + routing_params(), + HTTPMethod::InternalMethodCount)}; // This is always returned to avoid a null pointer dereference. + + // NOTE(EDev): This most likely will never run since the parser should handle this situation and close the connection before it gets here. + if (CROW_UNLIKELY(req.method >= HTTPMethod::InternalMethodCount)) + return found; + else if (req.method == HTTPMethod::Head) + { + *found = per_methods_[static_cast(method_actual)].trie.find(req.url); + // support HEAD requests using GET if not defined as method for the requested URL + if (!found->rule_index) + { + method_actual = HTTPMethod::Get; + *found = per_methods_[static_cast(method_actual)].trie.find(req.url); + if (!found->rule_index) // If a route is still not found, return a 404 without executing the rest of the HEAD specific code. + { + CROW_LOG_DEBUG << "Cannot match rules " << req.url; + res = response(404); //TODO(EDev): Should this redirect to catchall? + res.end(); + return found; + } + } + + res.skip_body = true; + found->method = method_actual; + return found; + } + else if (req.method == HTTPMethod::Options) + { + std::string allow = "OPTIONS, HEAD, "; + + if (req.url == "/*") + { + for (int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i++) + { + if (static_cast(HTTPMethod::Head) == i) + continue; // HEAD is always allowed + + if (!per_methods_[i].trie.is_empty()) + { + allow += method_name(static_cast(i)) + ", "; + } + } + allow = allow.substr(0, allow.size() - 2); + res = response(204); + res.set_header("Allow", allow); + res.end(); + found->method = method_actual; + return found; + } + else + { + bool rules_matched = false; + for (int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i++) + { + if (per_methods_[i].trie.find(req.url).rule_index) + { + rules_matched = true; + + if (static_cast(HTTPMethod::Head) == i) + continue; // HEAD is always allowed + + allow += method_name(static_cast(i)) + ", "; + } + } + if (rules_matched) + { + allow = allow.substr(0, allow.size() - 2); + res = response(204); + res.set_header("Allow", allow); + res.end(); + found->method = method_actual; + return found; + } + else + { + CROW_LOG_DEBUG << "Cannot match rules " << req.url; + res = response(404); //TODO(EDev): Should this redirect to catchall? + res.end(); + return found; + } + } + } + else // Every request that isn't a HEAD or OPTIONS request + { + *found = per_methods_[static_cast(method_actual)].trie.find(req.url); + // TODO(EDev): maybe ending the else here would allow the requests coming from above (after removing the return statement) to be checked on whether they actually point to a route + if (!found->rule_index) + { + for (auto& per_method : per_methods_) + { + if (per_method.trie.find(req.url).rule_index) //Route found, but in another method + { + const std::string error_message(get_error(405, *found, req, res)); + CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(method_actual) << ". " << error_message; + res.end(); + return found; + } + } + //Route does not exist anywhere + + const std::string error_message(get_error(404, *found, req, res)); + CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". " << error_message; + res.end(); + return found; + } + + found->method = method_actual; + return found; + } + } + + template + void handle(request& req, response& res, routing_handle_result found) + { + HTTPMethod method_actual = found.method; + auto& rules = per_methods_[static_cast(method_actual)].rules; + unsigned rule_index = found.rule_index; + + if (rule_index >= rules.size()) + throw std::runtime_error("Trie internal structure corrupted!"); + + if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) + { + CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; + res = response(301); + + // TODO(ipkn) absolute url building + if (req.get_header_value("Host").empty()) + { + res.add_header("Location", req.url + "/"); + } + else + { + res.add_header("Location", "http://" + req.get_header_value("Host") + req.url + "/"); + } + res.end(); + return; + } + + CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast(req.method) << " / " << rules[rule_index]->get_methods(); + + try + { + auto& rule = rules[rule_index]; + handle_rule(rule, req, res, found.r_params); + } + catch (...) + { + exception_handler_(res); + res.end(); + return; + } + } + + template + typename std::enable_if::value != 0, void>::type + handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp) + { + if (!rule->mw_indices_.empty()) + { + auto& ctx = *reinterpret_cast(req.middleware_context); + auto& container = *reinterpret_cast(req.middleware_container); + detail::middleware_call_criteria_dynamic crit_fwd(rule->mw_indices_.indices()); + + auto glob_completion_handler = std::move(res.complete_request_handler_); + res.complete_request_handler_ = [] {}; + + detail::middleware_call_helper(crit_fwd, container, req, res, ctx); + + if (res.completed_) + { + glob_completion_handler(); + return; + } + + res.complete_request_handler_ = [&rule, &ctx, &container, &req, &res, glob_completion_handler] { + detail::middleware_call_criteria_dynamic crit_bwd(rule->mw_indices_.indices()); + + detail::after_handlers_call_helper< + decltype(crit_bwd), + std::tuple_size::value - 1, + typename App::context_t, + typename App::mw_container_t>(crit_bwd, container, ctx, req, res); + glob_completion_handler(); + }; + } + rule->handle(req, res, rp); + } + + template + typename std::enable_if::value == 0, void>::type + handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp) + { + rule->handle(req, res, rp); + } + + void debug_print() + { + for (int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i++) + { + Trie& trie_ = per_methods_[i].trie; + if (!trie_.is_empty()) + { + CROW_LOG_DEBUG << method_name(static_cast(i)); + trie_.debug_print(); + } + } + } + + std::vector& blueprints() + { + return blueprints_; + } + + std::function& exception_handler() + { + return exception_handler_; + } + + static void default_exception_handler(response& res) + { + // any uncaught exceptions become 500s + res = response(500); + + try + { + throw; + } + catch (const std::exception& e) + { + CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what(); + } + catch (...) + { + CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available."; + } + } + + private: + CatchallRule catchall_rule_; + + struct PerMethod + { + std::vector rules; + Trie trie; + + // rule index 0, 1 has special meaning; preallocate it to avoid duplication. + PerMethod(): + rules(2) {} + }; + std::array(HTTPMethod::InternalMethodCount)> per_methods_; + std::vector> all_rules_; + std::vector blueprints_; + std::function exception_handler_ = &default_exception_handler; + }; +} // namespace crow + + +namespace crow +{ + struct CORSHandler; + + /// Used for tuning CORS policies + struct CORSRules + { + friend struct crow::CORSHandler; + + /// Set Access-Control-Allow-Origin. Default is "*" + CORSRules& origin(const std::string& origin) + { + origin_ = origin; + return *this; + } + + /// Set Access-Control-Allow-Methods. Default is "*" + CORSRules& methods(crow::HTTPMethod method) + { + add_list_item(methods_, crow::method_name(method)); + return *this; + } + + /// Set Access-Control-Allow-Methods. Default is "*" + template + CORSRules& methods(crow::HTTPMethod method, Methods... method_list) + { + add_list_item(methods_, crow::method_name(method)); + methods(method_list...); + return *this; + } + + /// Set Access-Control-Allow-Headers. Default is "*" + CORSRules& headers(const std::string& header) + { + add_list_item(headers_, header); + return *this; + } + + /// Set Access-Control-Allow-Headers. Default is "*" + template + CORSRules& headers(const std::string& header, Headers... header_list) + { + add_list_item(headers_, header); + headers(header_list...); + return *this; + } + + /// Set Access-Control-Max-Age. Default is none + CORSRules& max_age(int max_age) + { + max_age_ = std::to_string(max_age); + return *this; + } + + /// Enable Access-Control-Allow-Credentials + CORSRules& allow_credentials() + { + allow_credentials_ = true; + return *this; + } + + /// Ignore CORS and don't send any headers + void ignore() + { + ignore_ = true; + } + + /// Handle CORS on specific prefix path + CORSRules& prefix(const std::string& prefix); + + /// Handle CORS for specific blueprint + CORSRules& blueprint(const Blueprint& bp); + + /// Global CORS policy + CORSRules& global(); + + private: + CORSRules() = delete; + CORSRules(CORSHandler* handler): + handler_(handler) {} + + /// build comma separated list + void add_list_item(std::string& list, const std::string& val) + { + if (list == "*") list = ""; + if (list.size() > 0) list += ", "; + list += val; + } + + /// Set header `key` to `value` if it is not set + void set_header_no_override(const std::string& key, const std::string& value, crow::response& res) + { + if (value.size() == 0) return; + if (!get_header_value(res.headers, key).empty()) return; + res.add_header(key, value); + } + + /// Set response headers + void apply(crow::response& res) + { + if (ignore_) return; + set_header_no_override("Access-Control-Allow-Origin", origin_, res); + set_header_no_override("Access-Control-Allow-Methods", methods_, res); + set_header_no_override("Access-Control-Allow-Headers", headers_, res); + set_header_no_override("Access-Control-Max-Age", max_age_, res); + if (allow_credentials_) set_header_no_override("Access-Control-Allow-Credentials", "true", res); + } + + bool ignore_ = false; + // TODO: support multiple origins that are dynamically selected + std::string origin_ = "*"; + std::string methods_ = "*"; + std::string headers_ = "*"; + std::string max_age_; + bool allow_credentials_ = false; + + CORSHandler* handler_; + }; + + /// CORSHandler is a global middleware for setting CORS headers. + + /// + /// By default, it sets Access-Control-Allow-Origin/Methods/Headers to "*". + /// The default behaviour can be changed with the `global()` cors rule. + /// Additional rules for prexies can be added with `prefix()`. + struct CORSHandler + { + struct context + {}; + + void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) + {} + + void after_handle(crow::request& req, crow::response& res, context& /*ctx*/) + { + auto& rule = find_rule(req.url); + rule.apply(res); + } + + /// Handle CORS on a specific prefix path + CORSRules& prefix(const std::string& prefix) + { + rules.emplace_back(prefix, CORSRules(this)); + return rules.back().second; + } + + /// Handle CORS for a specific blueprint + CORSRules& blueprint(const Blueprint& bp) + { + rules.emplace_back(bp.prefix(), CORSRules(this)); + return rules.back().second; + } + + /// Get the global CORS policy + CORSRules& global() + { + return default_; + } + + private: + CORSRules& find_rule(const std::string& path) + { + // TODO: use a trie in case of many rules + for (auto& rule : rules) + { + // Check if path starts with a rules prefix + if (path.rfind(rule.first, 0) == 0) + { + return rule.second; + } + } + return default_; + } + + std::vector> rules; + CORSRules default_ = CORSRules(this); + }; + + inline CORSRules& CORSRules::prefix(const std::string& prefix) + { + return handler_->prefix(prefix); + } + + inline CORSRules& CORSRules::blueprint(const Blueprint& bp) + { + return handler_->blueprint(bp); + } + + inline CORSRules& CORSRules::global() + { + return handler_->global(); + } + +} // namespace crow + +/** + * \file crow/app.h + * \brief This file includes the definition of the crow::Crow class, + * the crow::App and crow::SimpleApp aliases, and some macros. + * + * In this file are defined: + * - crow::Crow + * - crow::App + * - crow::SimpleApp + * - \ref CROW_ROUTE + * - \ref CROW_BP_ROUTE + * - \ref CROW_WEBSOCKET_ROUTE + * - \ref CROW_MIDDLEWARES + * - \ref CROW_CATCHALL_ROUTE + * - \ref CROW_BP_CATCHALL_ROUTE + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CROW_ENABLE_COMPRESSION +#endif // #ifdef CROW_ENABLE_COMPRESSION + + +#ifdef CROW_MSVC_WORKAROUND + +#define CROW_ROUTE(app, url) app.route_dynamic(url) // See the documentation in the comment below. +#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url) // See the documentation in the comment below. + +#else // #ifdef CROW_MSVC_WORKAROUND + +/** + * \def CROW_ROUTE(app, url) + * \brief Creates a route for app using a rule. + * + * It use crow::Crow::route_dynamic or crow::Crow::route to define + * a rule for your application. It's usage is like this: + * + * ```cpp + * auto app = crow::SimpleApp(); // or crow::App() + * CROW_ROUTE(app, "/") + * ([](){ + * return "

Hello, world!

"; + * }); + * ``` + * + * This is the recommended way to define routes in a crow application. + * \see [Page of guide "Routes"](https://crowcpp.org/master/guides/routes/). + */ +#define CROW_ROUTE(app, url) app.template route(url) + +/** + * \def CROW_BP_ROUTE(blueprint, url) + * \brief Creates a route for a blueprint using a rule. + * + * It may use crow::Blueprint::new_rule_dynamic or + * crow::Blueprint::new_rule_tagged to define a new rule for + * an given blueprint. It's usage is similar + * to CROW_ROUTE macro: + * + * ```cpp + * crow::Blueprint my_bp(); + * CROW_BP_ROUTE(my_bp, "/") + * ([](){ + * return "

Hello, world!

"; + * }); + * ``` + * + * This is the recommended way to define routes in a crow blueprint + * because of its compile-time capabilities. + * + * \see [Page of the guide "Blueprints"](https://crowcpp.org/master/guides/blueprints/). + */ +#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged(url) + +/** + * \def CROW_WEBSOCKET_ROUTE(app, url) + * \brief Defines WebSocket route for app. + * + * It binds a WebSocket route to app. Easy solution to implement + * WebSockets in your app. The usage syntax of this macro is + * like this: + * + * ```cpp + * auto app = crow::SimpleApp(); // or crow::App() + * CROW_WEBSOCKET_ROUTE(app, "/ws") + * .onopen([&](crow::websocket::connection& conn){ + * do_something(); + * }) + * .onclose([&](crow::websocket::connection& conn, const std::string& reason){ + * do_something(); + * }) + * .onmessage([&](crow::websocket::connection&, const std::string& data, bool is_binary){ + * if (is_binary) + * do_something(data); + * else + * do_something_else(data); + * }); + * ``` + * + * \see [Page of the guide "WebSockets"](https://crowcpp.org/master/guides/websockets/). + */ +#define CROW_WEBSOCKET_ROUTE(app, url) app.route(url).websocket::type>(&app) + +/** + * \def CROW_MIDDLEWARES(app, ...) + * \brief Enable a Middleware for an specific route in app + * or blueprint. + * + * It defines the usage of a Middleware in one route. And it + * can be used in both crow::SimpleApp (and crow::App) instances and + * crow::Blueprint. Its usage syntax is like this: + * + * ```cpp + * auto app = crow::SimpleApp(); // or crow::App() + * CROW_ROUTE(app, "/with_middleware") + * .CROW_MIDDLEWARES(app, LocalMiddleware) // Can be used more than one + * ([]() { // middleware. + * return "Hello world!"; + * }); + * ``` + * + * \see [Page of the guide "Middlewares"](https://crowcpp.org/master/guides/middleware/). + */ +#define CROW_MIDDLEWARES(app, ...) template middlewares::type, __VA_ARGS__>() + +#endif // #ifdef CROW_MSVC_WORKAROUND + +/** + * \def CROW_CATCHALL_ROUTE(app) + * \brief Defines a custom catchall route for app using a + * custom rule. + * + * It defines a handler when the client make a request for an + * undefined route. Instead of just reply with a `404` status + * code (default behavior), you can define a custom handler + * using this macro. + * + * \see [Page of the guide "Routes" (Catchall routes)](https://crowcpp.org/master/guides/routes/#catchall-routes). + */ +#define CROW_CATCHALL_ROUTE(app) app.catchall_route() + +/** + * \def CROW_BP_CATCHALL_ROUTE(blueprint) + * \brief Defines a custom catchall route for blueprint + * using a custom rule. + * + * It defines a handler when the client make a request for an + * undefined route in the blueprint. + * + * \see [Page of the guide "Blueprint" (Define a custom Catchall route)](https://crowcpp.org/master/guides/blueprints/#define-a-custom-catchall-route). + */ +#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule() + + +/** + * \namespace crow + * \brief The main namespace of the library. In this namespace + * is defined the most important classes and functions of the + * library. + * + * Within this namespace, the Crow class, Router class, Connection + * class, and other are defined. + */ +namespace crow +{ +#ifdef CROW_ENABLE_SSL + using ssl_context_t = asio::ssl::context; +#endif + /** + * \class Crow + * \brief The main server application class. + * + * Use crow::SimpleApp or crow::App instead of + * directly instantiate this class. + */ + template + class Crow + { + public: + /// \brief This is the crow application + using self_t = Crow; + + /// \brief The HTTP server + using server_t = Server; + +#ifdef CROW_ENABLE_SSL + /// \brief An HTTP server that runs on SSL with an SSLAdaptor + using ssl_server_t = Server; +#endif + Crow() + {} + + /// \brief Construct Crow with a subset of middleware + template + Crow(Ts&&... ts): + middlewares_(make_middleware_tuple(std::forward(ts)...)) + {} + + /// \brief Process an Upgrade request + /// + /// Currently used to upgrade an HTTP connection to a WebSocket connection + template + void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) + { + router_.handle_upgrade(req, res, adaptor); + } + + /// \brief Process only the method and URL of a request and provide a route (or an error response) + std::unique_ptr handle_initial(request& req, response& res) + { + return router_.handle_initial(req, res); + } + + /// \brief Process the fully parsed request and generate a response for it + void handle(request& req, response& res, std::unique_ptr& found) + { + router_.handle(req, res, *found); + } + + /// \brief Process a fully parsed request from start to finish (primarily used for debugging) + void handle_full(request& req, response& res) + { + auto found = handle_initial(req, res); + if (found->rule_index) + handle(req, res, found); + } + + /// \brief Create a dynamic route using a rule (**Use CROW_ROUTE instead**) + DynamicRule& route_dynamic(const std::string& rule) + { + return router_.new_rule_dynamic(rule); + } + + /// \brief Create a route using a rule (**Use CROW_ROUTE instead**) + template +#ifdef CROW_GCC83_WORKAROUND + auto& route(const std::string& rule) +#else + auto route(const std::string& rule) +#endif +#if defined CROW_CAN_USE_CPP17 && !defined CROW_GCC83_WORKAROUND + -> typename std::invoke_result), Router, const std::string&>::type +#elif !defined CROW_GCC83_WORKAROUND + -> typename std::result_of)(Router, const std::string&)>::type +#endif + { + return router_.new_rule_tagged(rule); + } + + /// \brief Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**) + CatchallRule& catchall_route() + { + return router_.catchall_rule(); + } + + /// \brief Set the default max payload size for websockets + self_t& websocket_max_payload(uint64_t max_payload) + { + max_payload_ = max_payload; + return *this; + } + + /// \brief Get the default max payload size for websockets + uint64_t websocket_max_payload() + { + return max_payload_; + } + + self_t& signal_clear() + { + signals_.clear(); + return *this; + } + + self_t& signal_add(int signal_number) + { + signals_.push_back(signal_number); + return *this; + } + + std::vector signals() + { + return signals_; + } + + /// \brief Set the port that Crow will handle requests on + self_t& port(std::uint16_t port) + { + port_ = port; + return *this; + } + + /// \brief Get the port that Crow will handle requests on + std::uint16_t port() + { + return port_; + } + + /// \brief Set the connection timeout in seconds (default is 5) + self_t& timeout(std::uint8_t timeout) + { + timeout_ = timeout; + return *this; + } + + /// \brief Set the server name + self_t& server_name(std::string server_name) + { + server_name_ = server_name; + return *this; + } + + /// \brief The IP address that Crow will handle requests on (default is 0.0.0.0) + self_t& bindaddr(std::string bindaddr) + { + bindaddr_ = bindaddr; + return *this; + } + + /// \brief Get the address that Crow will handle requests on + std::string bindaddr() + { + return bindaddr_; + } + + /// \brief Run the server on multiple threads using all available threads + self_t& multithreaded() + { + return concurrency(std::thread::hardware_concurrency()); + } + + /// \brief Run the server on multiple threads using a specific number + self_t& concurrency(std::uint16_t concurrency) + { + if (concurrency < 2) // Crow can have a minimum of 2 threads running + concurrency = 2; + concurrency_ = concurrency; + return *this; + } + + /// \brief Get the number of threads that server is using + std::uint16_t concurrency() + { + return concurrency_; + } + + /// \brief Set the server's log level + /// + /// Possible values are: + /// - crow::LogLevel::Debug (0) + /// - crow::LogLevel::Info (1) + /// - crow::LogLevel::Warning (2) + /// - crow::LogLevel::Error (3) + /// - crow::LogLevel::Critical (4) + self_t& loglevel(LogLevel level) + { + crow::logger::setLogLevel(level); + return *this; + } + + /// \brief Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB) + /// + /// Any streamed response is unaffected by Crow's timer, and therefore won't timeout before a response is fully sent. + self_t& stream_threshold(size_t threshold) + { + res_stream_threshold_ = threshold; + return *this; + } + + /// \brief Get the response body size (in bytes) beyond which Crow automatically streams responses + size_t& stream_threshold() + { + return res_stream_threshold_; + } + + + self_t& register_blueprint(Blueprint& blueprint) + { + router_.register_blueprint(blueprint); + return *this; + } + + /// \brief Set the function to call to handle uncaught exceptions generated in routes (Default generates error 500). + /// + /// The function must have the following signature: void(crow::response&). + /// It must set the response passed in argument to the function, which will be sent back to the client. + /// See Router::default_exception_handler() for the default implementation. + template + self_t& exception_handler(Func&& f) + { + router_.exception_handler() = std::forward(f); + return *this; + } + + std::function& exception_handler() + { + return router_.exception_handler(); + } + + /// \brief Set a custom duration and function to run on every tick + template + self_t& tick(Duration d, Func f) + { + tick_interval_ = std::chrono::duration_cast(d); + tick_function_ = f; + return *this; + } + +#ifdef CROW_ENABLE_COMPRESSION + + self_t& use_compression(compression::algorithm algorithm) + { + comp_algorithm_ = algorithm; + compression_used_ = true; + return *this; + } + + compression::algorithm compression_algorithm() + { + return comp_algorithm_; + } + + bool compression_used() const + { + return compression_used_; + } +#endif + + /// \brief Apply blueprints + void add_blueprint() + { +#if defined(__APPLE__) || defined(__MACH__) + if (router_.blueprints().empty()) return; +#endif + + for (Blueprint* bp : router_.blueprints()) + { + if (bp->static_dir().empty()) continue; + + auto static_dir_ = crow::utility::normalize_path(bp->static_dir()); + + bp->new_rule_tagged(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) { + utility::sanitize_filename(file_path_partial); + res.set_static_file_info_unsafe(static_dir_ + file_path_partial); + res.end(); + }); + } + + router_.validate_bp(); + } + + /// \brief Go through the rules, upgrade them if possible, and add them to the list of rules + void add_static_dir() + { + if (are_static_routes_added()) return; + auto static_dir_ = crow::utility::normalize_path(CROW_STATIC_DIRECTORY); + + route(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) { + utility::sanitize_filename(file_path_partial); + res.set_static_file_info_unsafe(static_dir_ + file_path_partial); + res.end(); + }); + set_static_routes_added(); + } + + /// \brief A wrapper for `validate()` in the router + void validate() + { + router_.validate(); + } + + /// \brief Run the server + void run() + { +#ifndef CROW_DISABLE_STATIC_DIR + add_blueprint(); + add_static_dir(); +#endif + validate(); + +#ifdef CROW_ENABLE_SSL + if (ssl_used_) + { + ssl_server_ = std::move(std::unique_ptr(new ssl_server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_))); + ssl_server_->set_tick_function(tick_interval_, tick_function_); + ssl_server_->signal_clear(); + for (auto snum : signals_) + { + ssl_server_->signal_add(snum); + } + notify_server_start(); + ssl_server_->run(); + } + else +#endif + { + server_ = std::move(std::unique_ptr(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, nullptr))); + server_->set_tick_function(tick_interval_, tick_function_); + for (auto snum : signals_) + { + server_->signal_add(snum); + } + notify_server_start(); + server_->run(); + } + } + + /// \brief Non-blocking version of \ref run() + /// + /// The output from this method needs to be saved into a variable! + /// Otherwise the call will be made on the same thread. + std::future run_async() + { + return std::async(std::launch::async, [&] { + this->run(); + }); + } + + /// \brief Stop the server + void stop() + { +#ifdef CROW_ENABLE_SSL + if (ssl_used_) + { + if (ssl_server_) { ssl_server_->stop(); } + } + else +#endif + { + // TODO(EDev): Move these 6 lines to a method in http_server. + std::vector websockets_to_close = websockets_; + for (auto websocket : websockets_to_close) + { + CROW_LOG_INFO << "Quitting Websocket: " << websocket; + websocket->close("Server Application Terminated"); + } + if (server_) { server_->stop(); } + } + } + + void add_websocket(crow::websocket::connection* conn) + { + websockets_.push_back(conn); + } + + void remove_websocket(crow::websocket::connection* conn) + { + websockets_.erase(std::remove(websockets_.begin(), websockets_.end(), conn), websockets_.end()); + } + + /// \brief Print the routing paths defined for each HTTP method + void debug_print() + { + CROW_LOG_DEBUG << "Routing:"; + router_.debug_print(); + } + + +#ifdef CROW_ENABLE_SSL + + /// \brief Use certificate and key files for SSL + self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename) + { + ssl_used_ = true; + ssl_context_.set_verify_mode(asio::ssl::verify_peer); + ssl_context_.set_verify_mode(asio::ssl::verify_client_once); + ssl_context_.use_certificate_file(crt_filename, ssl_context_t::pem); + ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem); + ssl_context_.set_options( + asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); + return *this; + } + + /// \brief Use `.pem` file for SSL + self_t& ssl_file(const std::string& pem_filename) + { + ssl_used_ = true; + ssl_context_.set_verify_mode(asio::ssl::verify_peer); + ssl_context_.set_verify_mode(asio::ssl::verify_client_once); + ssl_context_.load_verify_file(pem_filename); + ssl_context_.set_options( + asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); + return *this; + } + + /// \brief Use certificate chain and key files for SSL + self_t& ssl_chainfile(const std::string& crt_filename, const std::string& key_filename) + { + ssl_used_ = true; + ssl_context_.set_verify_mode(asio::ssl::verify_peer); + ssl_context_.set_verify_mode(asio::ssl::verify_client_once); + ssl_context_.use_certificate_chain_file(crt_filename); + ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem); + ssl_context_.set_options( + asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); + return *this; + } + + self_t& ssl(asio::ssl::context&& ctx) + { + ssl_used_ = true; + ssl_context_ = std::move(ctx); + return *this; + } + + bool ssl_used() const + { + return ssl_used_; + } +#else + + template + self_t& ssl_file(T&&, Remain&&...) + { + // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. + static_assert( + // make static_assert dependent to T; always false + std::is_base_of::value, + "Define CROW_ENABLE_SSL to enable ssl support."); + return *this; + } + + template + self_t& ssl_chainfile(T&&, Remain&&...) + { + // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. + static_assert( + // make static_assert dependent to T; always false + std::is_base_of::value, + "Define CROW_ENABLE_SSL to enable ssl support."); + return *this; + } + + template + self_t& ssl(T&&) + { + // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. + static_assert( + // make static_assert dependent to T; always false + std::is_base_of::value, + "Define CROW_ENABLE_SSL to enable ssl support."); + return *this; + } + + bool ssl_used() const + { + return false; + } +#endif + + // middleware + using context_t = detail::context; + using mw_container_t = std::tuple; + template + typename T::context& get_context(const request& req) + { + static_assert(black_magic::contains::value, "App doesn't have the specified middleware type."); + auto& ctx = *reinterpret_cast(req.middleware_context); + return ctx.template get(); + } + + template + T& get_middleware() + { + return utility::get_element_by_type(middlewares_); + } + + /// \brief Wait until the server has properly started + void wait_for_server_start() + { + { + std::unique_lock lock(start_mutex_); + if (!server_started_) + cv_started_.wait(lock); + } + if (server_) + server_->wait_for_start(); +#ifdef CROW_ENABLE_SSL + else if (ssl_server_) + ssl_server_->wait_for_start(); +#endif + } + + private: + template + std::tuple make_middleware_tuple(Ts&&... ts) + { + auto fwd = std::forward_as_tuple((ts)...); + return std::make_tuple( + std::forward( + black_magic::tuple_extract(fwd))...); + } + + /// \brief Notify anything using \ref wait_for_server_start() to proceed + void notify_server_start() + { + std::unique_lock lock(start_mutex_); + server_started_ = true; + cv_started_.notify_all(); + } + + void set_static_routes_added() { + static_routes_added_ = true; + } + + bool are_static_routes_added() { + return static_routes_added_; + } + + private: + std::uint8_t timeout_{5}; + uint16_t port_ = 80; + uint16_t concurrency_ = 2; + uint64_t max_payload_{UINT64_MAX}; + std::string server_name_ = std::string("Crow/") + VERSION; + std::string bindaddr_ = "0.0.0.0"; + size_t res_stream_threshold_ = 1048576; + Router router_; + bool static_routes_added_{false}; + +#ifdef CROW_ENABLE_COMPRESSION + compression::algorithm comp_algorithm_; + bool compression_used_{false}; +#endif + + std::chrono::milliseconds tick_interval_; + std::function tick_function_; + + std::tuple middlewares_; + +#ifdef CROW_ENABLE_SSL + std::unique_ptr ssl_server_; + bool ssl_used_{false}; + ssl_context_t ssl_context_{asio::ssl::context::sslv23}; +#endif + + std::unique_ptr server_; + + std::vector signals_{SIGINT, SIGTERM}; + + bool server_started_{false}; + std::condition_variable cv_started_; + std::mutex start_mutex_; + std::vector websockets_; + }; + + /// \brief Alias of Crow. Useful if you want + /// a instance of an Crow application that require Middlewares + template + using App = Crow; + + /// \brief Alias of Crow<>. Useful if you want a instance of + /// an Crow application that doesn't require of Middlewares + using SimpleApp = Crow<>; +} // namespace crow + diff --git a/benchmark/crow/main.cpp b/benchmark/crow/main.cpp new file mode 100644 index 0000000..034cf78 --- /dev/null +++ b/benchmark/crow/main.cpp @@ -0,0 +1,17 @@ +#include "crow_all.h" + +class CustomLogger : public crow::ILogHandler { +public: + void log(std::string, crow::LogLevel) {} +}; + +int main() { + CustomLogger logger; + crow::logger::setHandler(&logger); + + crow::SimpleApp app; + + CROW_ROUTE(app, "/")([]() { return "Hello world!"; }); + + app.port(8080).multithreaded().run(); +} diff --git a/benchmark/download.sh b/benchmark/download.sh new file mode 100755 index 0000000..90c959e --- /dev/null +++ b/benchmark/download.sh @@ -0,0 +1,2 @@ +rm -f httplib.h +wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h diff --git a/cmake/FindBrotli.cmake b/cmake/FindBrotli.cmake new file mode 100644 index 0000000..2048e55 --- /dev/null +++ b/cmake/FindBrotli.cmake @@ -0,0 +1,168 @@ +# A simple FindBrotli package for Cmake's find_package function. +# Note: This find package doesn't have version support, as the version file doesn't seem to be installed on most systems. +# +# If you want to find the static packages instead of shared (the default), define BROTLI_USE_STATIC_LIBS as TRUE. +# The targets will have the same names, but it will use the static libs. +# +# Valid find_package COMPONENTS names: "decoder", "encoder", and "common" +# Note that if you're requiring "decoder" or "encoder", then "common" will be automatically added as required. +# +# Defines the libraries (if found): Brotli::decoder, Brotli::encoder, Brotli::common +# and the includes path variable: Brotli_INCLUDE_DIR +# +# If it's failing to find the libraries, try setting BROTLI_ROOT_DIR to the folder containing your library & include dir. + +# If they asked for a specific version, warn/fail since we don't support it. +# TODO: if they start distributing the version somewhere, implement finding it. +# See https://github.com/google/brotli/issues/773#issuecomment-579133187 +if(Brotli_FIND_VERSION) + set(_brotli_version_error_msg "FindBrotli.cmake doesn't have version support!") + # If the package is required, throw a fatal error + # Otherwise, if not running quietly, we throw a warning + if(Brotli_FIND_REQUIRED) + message(FATAL_ERROR "${_brotli_version_error_msg}") + elseif(NOT Brotli_FIND_QUIETLY) + message(WARNING "${_brotli_version_error_msg}") + endif() +endif() + +# Since both decoder & encoder require the common lib, force its requirement.. +# if the user is requiring either of those other libs. +if(Brotli_FIND_REQUIRED_decoder OR Brotli_FIND_REQUIRED_encoder) + set(Brotli_FIND_REQUIRED_common TRUE) +endif() + +# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES +# Credit to FindOpenSSL.cmake for this +if(BROTLI_USE_STATIC_LIBS) + set(_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif() +endif() + +# Make PkgConfig optional, since some users (mainly Windows) don't have it. +# But it's a lot more clean than manually using find_library. +find_package(PkgConfig QUIET) + +# Only used if the PkgConfig libraries aren't used. +find_path(Brotli_INCLUDE_DIR + NAMES + "brotli/decode.h" + "brotli/encode.h" + HINTS + ${BROTLI_ROOT_DIR} + PATH_SUFFIXES + "include" + "includes" + DOC "The path to Brotli's include directory." +) +# Hides this var from the GUI +mark_as_advanced(Brotli_INCLUDE_DIR) + +# Just used for PkgConfig stuff in the loop below +set(_brotli_stat_str "") +if(BROTLI_USE_STATIC_LIBS) + set(_brotli_stat_str "_STATIC") +endif() + +# Each string here is "ComponentName;LiteralName" (the semi-colon is a delimiter) +foreach(_listvar "common;common" "decoder;dec" "encoder;enc") + # Split the component name and literal library name from the listvar + list(GET _listvar 0 _component_name) + list(GET _listvar 1 _libname) + + # NOTE: We can't rely on PkgConf for static libs since the upstream static lib support is broken + # See https://github.com/google/brotli/issues/795 + # TODO: whenever their issue is fixed upstream, remove this "AND NOT BROTLI_USE_STATIC_LIBS" check + if(PKG_CONFIG_FOUND AND NOT BROTLI_USE_STATIC_LIBS) + # These need to be GLOBAL for MinGW when making ALIAS libraries against them. + # Have to postfix _STATIC on the name to tell PkgConfig to find the static libs. + pkg_check_modules(Brotli_${_component_name}${_brotli_stat_str} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname}) + endif() + + # Check if the target was already found by Pkgconf + if(TARGET PkgConfig::Brotli_${_component_name}${_brotli_stat_str}) + # ALIAS since we don't want the PkgConfig namespace on the Cmake library (for end-users) + add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str}) + + # Tells HANDLE_COMPONENTS we found the component + set(Brotli_${_component_name}_FOUND TRUE) + if(Brotli_FIND_REQUIRED_${_component_name}) + # If the lib is required, we can add its literal path as a required var for FindPackageHandleStandardArgs + # Since it won't accept the PkgConfig targets + if(BROTLI_USE_STATIC_LIBS) + list(APPEND _brotli_req_vars "Brotli_${_component_name}_STATIC_LIBRARIES") + else() + list(APPEND _brotli_req_vars "Brotli_${_component_name}_LINK_LIBRARIES") + endif() + endif() + + # Skip searching for the libs with find_library since it was already found by Pkgconf + continue() + endif() + + if(Brotli_FIND_REQUIRED_${_component_name}) + # If it's required, we can set the name used in find_library as a required var for FindPackageHandleStandardArgs + list(APPEND _brotli_req_vars "Brotli_${_component_name}") + endif() + + list(APPEND _brotli_lib_names + "brotli${_libname}" + "libbrotli${_libname}" + ) + if(BROTLI_USE_STATIC_LIBS) + # Postfix "-static" to the libnames since we're looking for static libs + list(TRANSFORM _brotli_lib_names APPEND "-static") + endif() + + find_library(Brotli_${_component_name} + NAMES ${_brotli_lib_names} + HINTS ${BROTLI_ROOT_DIR} + PATH_SUFFIXES + "lib" + "lib64" + "libs" + "libs64" + "lib/x86_64-linux-gnu" + ) + # Hide the library variable from the Cmake GUI + mark_as_advanced(Brotli_${_component_name}) + + # Unset since otherwise it'll stick around for the next loop and break things + unset(_brotli_lib_names) + + # Check if find_library found the library + if(Brotli_${_component_name}) + # Tells HANDLE_COMPONENTS we found the component + set(Brotli_${_component_name}_FOUND TRUE) + + add_library("Brotli::${_component_name}" UNKNOWN IMPORTED) + # Attach the literal library and include dir to the IMPORTED target for the end-user + set_target_properties("Brotli::${_component_name}" PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}" + IMPORTED_LOCATION "${Brotli_${_component_name}}" + ) + else() + # Tells HANDLE_COMPONENTS we found the component + set(Brotli_${_component_name}_FOUND FALSE) + endif() +endforeach() + +include(FindPackageHandleStandardArgs) +# Sets Brotli_FOUND, and fails the find_package(Brotli) call if it was REQUIRED but missing libs. +find_package_handle_standard_args(Brotli + FOUND_VAR + Brotli_FOUND + REQUIRED_VARS + Brotli_INCLUDE_DIR + ${_brotli_req_vars} + HANDLE_COMPONENTS +) + +# Restore the original find library ordering +if(BROTLI_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +endif() diff --git a/cmake/httplibConfig.cmake.in b/cmake/httplibConfig.cmake.in new file mode 100644 index 0000000..19dbe69 --- /dev/null +++ b/cmake/httplibConfig.cmake.in @@ -0,0 +1,105 @@ +# Generates a macro to auto-configure everything +@PACKAGE_INIT@ + +# Setting these here so they're accessible after install. +# Might be useful for some users to check which settings were used. +set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@) +set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@) +set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@) +set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@) +set(HTTPLIB_VERSION @PROJECT_VERSION@) + +include(CMakeFindDependencyMacro) + +# We add find_dependency calls here to not make the end-user have to call them. +find_dependency(Threads) +if(@HTTPLIB_IS_USING_OPENSSL@) + # OpenSSL COMPONENTS were added in Cmake v3.11 + if(CMAKE_VERSION VERSION_LESS "3.11") + find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@) + else() + # Once the COMPONENTS were added, they were made optional when not specified. + # Since we use both, we need to search for both. + find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL) + endif() + set(httplib_OpenSSL_FOUND ${OpenSSL_FOUND}) +endif() +if(@HTTPLIB_IS_USING_ZLIB@) + find_dependency(ZLIB) + set(httplib_ZLIB_FOUND ${ZLIB_FOUND}) +endif() + +if(@HTTPLIB_IS_USING_BROTLI@) + # Needed so we can use our own FindBrotli.cmake in this file. + # Note that the FindBrotli.cmake file is installed in the same dir as this file. + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@) + find_dependency(Brotli COMPONENTS common encoder decoder) + set(httplib_Brotli_FOUND ${Brotli_FOUND}) +endif() + +if(@HTTPLIB_IS_USING_ZSTD@) + set(httplib_fd_zstd_quiet_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(httplib_fd_zstd_quiet_arg QUIET) + endif() + set(httplib_fd_zstd_required_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + set(httplib_fd_zstd_required_arg REQUIRED) + endif() + find_package(zstd QUIET) + if(NOT zstd_FOUND) + find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd) + + if(TARGET PkgConfig::zstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() + endif() + endif() + set(httplib_zstd_FOUND ${zstd_FOUND}) +endif() + +# Mildly useful for end-users +# Not really recommended to be used though +set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@") +# Lets the end-user find the header path with the header appended +# This is helpful if you're using Cmake's pre-compiled header feature +set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h") + +check_required_components(httplib) + +# Brings in the target library, but only if all required components are found +if(NOT DEFINED httplib_FOUND OR httplib_FOUND) + include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake") +endif() + +# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib) +include(FindPackageMessage) +if(TARGET httplib::httplib) + set(HTTPLIB_FOUND TRUE) + + # Since the compiled version has a lib, show that in the message + if(@HTTPLIB_COMPILE@) + # The list of configurations is most likely just 1 unless they installed a debug & release + get_target_property(_httplib_configs httplib::httplib "IMPORTED_CONFIGURATIONS") + # Need to loop since the "IMPORTED_LOCATION" property isn't want we want. + # Instead, we need to find the IMPORTED_LOCATION_RELEASE or IMPORTED_LOCATION_DEBUG which has the lib path. + foreach(_httplib_conf "${_httplib_configs}") + # Grab the path to the lib and sets it to HTTPLIB_LIBRARY + get_target_property(HTTPLIB_LIBRARY httplib::httplib "IMPORTED_LOCATION_${_httplib_conf}") + # Check if we found it + if(HTTPLIB_LIBRARY) + break() + endif() + endforeach() + + unset(_httplib_configs) + unset(_httplib_conf) + + find_package_message(httplib "Found httplib: ${HTTPLIB_LIBRARY} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_LIBRARY}][${HTTPLIB_HEADER_PATH}]") + else() + find_package_message(httplib "Found httplib: ${HTTPLIB_HEADER_PATH} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_HEADER_PATH}]") + endif() +endif() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4a55b0d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + http: + build: . + ports: + - "8080:80" + volumes: + - ./docker/html:/html diff --git a/docker/html/index.html b/docker/html/index.html new file mode 100644 index 0000000..1abe978 --- /dev/null +++ b/docker/html/index.html @@ -0,0 +1,21 @@ + + + +Welcome to cpp-httplib! + + + +

Welcome to cpp-httplib!

+

If you see this page, the cpp-httplib web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +github.com/yhirose/cpp-httplib.
+ +

Thank you for using cpp-httplib.

+ + diff --git a/docker/main.cc b/docker/main.cc new file mode 100644 index 0000000..62d0c74 --- /dev/null +++ b/docker/main.cc @@ -0,0 +1,81 @@ +// +// main.cc +// +// Copyright (c) 2025 Yuji Hirose. All rights reserved. +// MIT License +// + +#include +#include +#include +#include +#include +#include + +#include + +constexpr auto error_html = R"( +{} {} + +

404 Not Found

+
cpp-httplib/{}
+ + +)"; + +void sigint_handler(int s) { exit(1); } + +std::string time_local() { + auto p = std::chrono::system_clock::now(); + auto t = std::chrono::system_clock::to_time_t(p); + + std::stringstream ss; + ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z"); + return ss.str(); +} + +std::string log(auto &req, auto &res) { + auto remote_user = "-"; // TODO: + auto request = std::format("{} {} {}", req.method, req.path, req.version); + auto body_bytes_sent = res.get_header_value("Content-Length"); + auto http_referer = "-"; // TODO: + auto http_user_agent = req.get_header_value("User-Agent", "-"); + + // NOTE: From NGINX default access log format + // log_format combined '$remote_addr - $remote_user [$time_local] ' + // '"$request" $status $body_bytes_sent ' + // '"$http_referer" "$http_user_agent"'; + return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr, + remote_user, time_local(), request, res.status, + body_bytes_sent, http_referer, http_user_agent); +} + +int main(int argc, const char **argv) { + signal(SIGINT, sigint_handler); + + auto base_dir = "./html"; + auto host = "0.0.0.0"; + auto port = 80; + + httplib::Server svr; + + svr.set_error_handler([](auto & /*req*/, auto &res) { + auto body = + std::format(error_html, res.status, httplib::status_message(res.status), + CPPHTTPLIB_VERSION); + + res.set_content(body, "text/html"); + }); + + svr.set_logger( + [](auto &req, auto &res) { std::cout << log(req, res) << std::endl; }); + + svr.set_mount_point("/", base_dir); + + std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port) + << std::endl; + + auto ret = svr.listen(host, port); + + return ret ? 0 : 1; +} diff --git a/example/Dockerfile.hello b/example/Dockerfile.hello new file mode 100644 index 0000000..d1188ae --- /dev/null +++ b/example/Dockerfile.hello @@ -0,0 +1,12 @@ +FROM alpine as builder +WORKDIR /src/example +RUN apk add g++ make openssl-dev zlib-dev brotli-dev +COPY ./httplib.h /src +COPY ./example/hello.cc /src/example +COPY ./example/Makefile /src/example +RUN make hello + +FROM alpine +RUN apk --no-cache add brotli libstdc++ +COPY --from=builder /src/example/hello /bin/hello +CMD ["/bin/hello"] diff --git a/example/Makefile b/example/Makefile index 0add1e4..ba68359 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,36 +1,64 @@ - #CXX = clang++ -CXXFLAGS = -std=c++14 -I.. -Wall -Wextra -pthread -OPENSSL_DIR = /usr/local/opt/openssl +CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread + +PREFIX ?= $(shell brew --prefix) + +OPENSSL_DIR = $(PREFIX)/opt/openssl@3 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto + +ifneq ($(OS), Windows_NT) + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S), Darwin) + OPENSSL_SUPPORT += -framework CoreFoundation -framework Security + endif +endif + ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz -all: server client hello simplesvr upload redirect benchmark +BROTLI_DIR = $(PREFIX)/opt/brotli +BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec + +all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client server : server.cc ../httplib.h Makefile - $(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) client : client.cc ../httplib.h Makefile - $(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) hello : hello.cc ../httplib.h Makefile - $(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) + +simplecli : simplecli.cc ../httplib.h Makefile + $(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) simplesvr : simplesvr.cc ../httplib.h Makefile - $(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) upload : upload.cc ../httplib.h Makefile - $(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) redirect : redirect.cc ../httplib.h Makefile - $(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) + +ssesvr : ssesvr.cc ../httplib.h Makefile + $(CXX) -o ssesvr $(CXXFLAGS) ssesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) + +ssecli : ssecli.cc ../httplib.h Makefile + $(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) benchmark : benchmark.cc ../httplib.h Makefile - $(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + $(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) + +one_time_request : one_time_request.cc ../httplib.h Makefile + $(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) + +server_and_client : server_and_client.cc ../httplib.h Makefile + $(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) pem: openssl genrsa 2048 > key.pem openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem clean: - rm server client hello simplesvr upload redirect benchmark *.pem + rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client *.pem diff --git a/example/benchmark.cc b/example/benchmark.cc index 8e300b9..433cc67 100644 --- a/example/benchmark.cc +++ b/example/benchmark.cc @@ -26,7 +26,7 @@ int main(void) { for (int i = 0; i < 3; i++) { StopWatch sw(to_string(i).c_str()); auto res = cli.Post("/post", body, "application/octet-stream"); - assert(res->status == 200); + assert(res->status == httplib::StatusCode::OK_200); } return 0; diff --git a/example/client.cc b/example/client.cc index 926f210..a9b0fc0 100644 --- a/example/client.cc +++ b/example/client.cc @@ -23,12 +23,12 @@ int main(void) { httplib::Client cli("localhost", 8080); #endif - auto res = cli.Get("/hi"); - if (res) { + if (auto res = cli.Get("/hi")) { cout << res->status << endl; cout << res->get_header_value("Content-Type") << endl; cout << res->body << endl; } else { + cout << "error code: " << res.error() << std::endl; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT auto result = cli.get_openssl_verify_result(); if (result) { diff --git a/example/client.vcxproj b/example/client.vcxproj index 1abb773..bc90190 100644 --- a/example/client.vcxproj +++ b/example/client.vcxproj @@ -22,34 +22,34 @@ {6DB1FC63-B153-4279-92B7-D8A11AF285D6} Win32Proj client - 10.0.15063.0 + 10.0 Application true Unicode - v141 + v143 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/example/hello.cc b/example/hello.cc index 1ef6b98..38d25a6 100644 --- a/example/hello.cc +++ b/example/hello.cc @@ -15,5 +15,5 @@ int main(void) { res.set_content("Hello World!", "text/plain"); }); - svr.listen("localhost", 1234); + svr.listen("0.0.0.0", 8080); } diff --git a/example/one_time_request.cc b/example/one_time_request.cc new file mode 100644 index 0000000..9a8ac34 --- /dev/null +++ b/example/one_time_request.cc @@ -0,0 +1,56 @@ +#include +#include + +using namespace httplib; + +const char *HOST = "localhost"; +const int PORT = 1234; + +void one_time_request_server(const char *label) { + std::thread th; + Server svr; + + svr.Get("/hi", [&](const Request & /*req*/, Response &res) { + res.set_content(std::string("Hello from ") + label, "text/plain"); + + // Stop server + th = std::thread([&]() { svr.stop(); }); + }); + + svr.listen(HOST, PORT); + th.join(); + + std::cout << label << " ended..." << std::endl; +} + +void send_request(const char *label) { + Client cli(HOST, PORT); + + std::cout << "Send " << label << " request" << std::endl; + auto res = cli.Get("/hi"); + + if (res) { + std::cout << res->body << std::endl; + } else { + std::cout << "Request error: " + to_string(res.error()) << std::endl; + } +} + +int main(void) { + auto th1 = std::thread([&]() { one_time_request_server("Server #1"); }); + auto th2 = std::thread([&]() { one_time_request_server("Server #2"); }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + send_request("1st"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + send_request("2nd"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + send_request("3rd"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + th1.join(); + th2.join(); +} diff --git a/example/server.vcxproj b/example/server.vcxproj index 31ff203..4dfb9e0 100644 --- a/example/server.vcxproj +++ b/example/server.vcxproj @@ -18,38 +18,41 @@ x64 + + + {864CD288-050A-4C8B-9BEF-3048BD876C5B} Win32Proj sample - 10.0.15063.0 + 10.0 Application true Unicode - v141 + v143 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 Application false true Unicode - v141 + v143 @@ -151,9 +154,6 @@ Ws2_32.lib;%(AdditionalDependencies) - - - diff --git a/example/server_and_client.cc b/example/server_and_client.cc new file mode 100644 index 0000000..34bf852 --- /dev/null +++ b/example/server_and_client.cc @@ -0,0 +1,90 @@ +// +// server_and_client.cc +// +// Copyright (c) 2025 Yuji Hirose. All rights reserved. +// MIT License +// + +#include +#include +#include + +using namespace httplib; + +std::string dump_headers(const Headers &headers) { + std::string s; + char buf[BUFSIZ]; + + for (auto it = headers.begin(); it != headers.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str()); + s += buf; + } + + return s; +} + +void logger(const Request &req, const Response &res) { + std::string s; + char buf[BUFSIZ]; + + s += "================================\n"; + + snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(), + req.version.c_str(), req.path.c_str()); + s += buf; + + std::string query; + for (auto it = req.params.begin(); it != req.params.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%c%s=%s", + (it == req.params.begin()) ? '?' : '&', x.first.c_str(), + x.second.c_str()); + query += buf; + } + snprintf(buf, sizeof(buf), "%s\n", query.c_str()); + s += buf; + + s += dump_headers(req.headers); + + s += "--------------------------------\n"; + + snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str()); + s += buf; + s += dump_headers(res.headers); + s += "\n"; + + if (!res.body.empty()) { s += res.body; } + + s += "\n"; + + std::cout << s; +} + +int main(void) { + // Server + Server svr; + svr.set_logger(logger); + + svr.Post("/post", [&](const Request & /*req*/, Response &res) { + res.set_content("POST", "text/plain"); + }); + + auto th = std::thread([&]() { svr.listen("localhost", 8080); }); + + auto se = detail::scope_exit([&] { + svr.stop(); + th.join(); + }); + + svr.wait_until_ready(); + + // Client + Client cli{"localhost", 8080}; + + std::string body = R"({"hello": "world"})"; + + auto res = cli.Post("/post", body, "application/json"); + std::cout << "--------------------------------" << std::endl; + std::cout << to_string(res.error()) << std::endl; +} diff --git a/example/simplecli.cc b/example/simplecli.cc new file mode 100644 index 0000000..b005e40 --- /dev/null +++ b/example/simplecli.cc @@ -0,0 +1,29 @@ +// +// simplecli.cc +// +// Copyright (c) 2019 Yuji Hirose. All rights reserved. +// MIT License +// + +#include +#include + +using namespace std; + +int main(void) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + auto scheme_host_port = "https://localhost:8080"; +#else + auto scheme_host_port = "http://localhost:8080"; +#endif + + if (auto res = httplib::Client(scheme_host_port).Get("/hi")) { + cout << res->status << endl; + cout << res->get_header_value("Content-Type") << endl; + cout << res->body << endl; + } else { + cout << res.error() << endl; + } + + return 0; +} diff --git a/example/simplesvr.cc b/example/simplesvr.cc index dcc97f6..17a9e98 100644 --- a/example/simplesvr.cc +++ b/example/simplesvr.cc @@ -46,7 +46,7 @@ string dump_multipart_files(const MultipartFormDataMap &files) { snprintf(buf, sizeof(buf), "content type: %s\n", file.content_type.c_str()); s += buf; - snprintf(buf, sizeof(buf), "text length: %lu\n", file.content.size()); + snprintf(buf, sizeof(buf), "text length: %zu\n", file.content.size()); s += buf; s += "----------------\n"; @@ -122,9 +122,12 @@ int main(int argc, const char **argv) { auto base_dir = "./"; if (argc > 2) { base_dir = argv[2]; } - svr.set_base_dir(base_dir); + if (!svr.set_mount_point("/", base_dir)) { + cout << "The specified base directory doesn't exist..."; + return 1; + } - cout << "The server started at port " << port << "..."; + cout << "The server started at port " << port << "..." << endl; svr.listen("localhost", port); diff --git a/example/ssecli.cc b/example/ssecli.cc new file mode 100644 index 0000000..2c93822 --- /dev/null +++ b/example/ssecli.cc @@ -0,0 +1,21 @@ +// +// ssecli.cc +// +// Copyright (c) 2019 Yuji Hirose. All rights reserved. +// MIT License +// + +#include +#include + +using namespace std; + +int main(void) { + httplib::Client("http://localhost:1234") + .Get("/event1", [&](const char *data, size_t data_length) { + std::cout << string(data, data_length); + return true; + }); + + return 0; +} diff --git a/example/ssesvr.cc b/example/ssesvr.cc new file mode 100644 index 0000000..547b864 --- /dev/null +++ b/example/ssesvr.cc @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace httplib; +using namespace std; + +class EventDispatcher { +public: + EventDispatcher() {} + + void wait_event(DataSink *sink) { + unique_lock lk(m_); + int id = id_; + cv_.wait(lk, [&] { return cid_ == id; }); + sink->write(message_.data(), message_.size()); + } + + void send_event(const string &message) { + lock_guard lk(m_); + cid_ = id_++; + message_ = message; + cv_.notify_all(); + } + +private: + mutex m_; + condition_variable cv_; + atomic_int id_{0}; + atomic_int cid_{-1}; + string message_; +}; + +const auto html = R"( + + + + +SSE demo + + + + + +)"; + +int main(void) { + EventDispatcher ed; + + Server svr; + + svr.Get("/", [&](const Request & /*req*/, Response &res) { + res.set_content(html, "text/html"); + }); + + svr.Get("/event1", [&](const Request & /*req*/, Response &res) { + cout << "connected to event1..." << endl; + res.set_chunked_content_provider("text/event-stream", + [&](size_t /*offset*/, DataSink &sink) { + ed.wait_event(&sink); + return true; + }); + }); + + svr.Get("/event2", [&](const Request & /*req*/, Response &res) { + cout << "connected to event2..." << endl; + res.set_chunked_content_provider("text/event-stream", + [&](size_t /*offset*/, DataSink &sink) { + ed.wait_event(&sink); + return true; + }); + }); + + thread t([&] { + int id = 0; + while (true) { + this_thread::sleep_for(chrono::seconds(1)); + cout << "send event: " << id << std::endl; + std::stringstream ss; + ss << "data: " << id << "\n\n"; + ed.send_event(ss.str()); + id++; + } + }); + + svr.listen("localhost", 1234); +} diff --git a/example/upload.cc b/example/upload.cc index 4d4145f..1e4f242 100644 --- a/example/upload.cc +++ b/example/upload.cc @@ -13,7 +13,8 @@ using namespace std; const char *html = R"(
- + +