Compare commits

..

37 Commits

Author SHA1 Message Date
yhirose 8438df4a95 Release v0.16.0 2024-06-11 18:20:47 -04:00
Jiwoo Park 67fd7e3d09 Change library name to cpp-httplib (#1854) 2024-06-10 13:18:19 -04:00
Rainer Schielke d44031615d New function SSLServer::update_certs. Allows to update certificates while server is running (#1827)
* New function SSLServer::update_certs. Allows to update certificates while server is running

* New function SSLServer::update_certs. Added unit test

---------

Co-authored-by: CEU\schielke <Rainer.Schielke@heidelberg.com>
2024-06-03 09:37:40 -04:00
Sean Quinn 98cc1ec344 Allow hex for ip6 literal addr, fix #1800 (#1830)
* Allow hex for ip6 literal addr, fix #1800

* Add UT for ipv6 + Universal client implementation

* add /n at EOF
2024-05-26 08:57:07 -04:00
Vladimír Chlup 25b1e0d906 Tweak CI & fix macOS prefix (#1843)
* Use brew prefix or given one

* Polish CI workflow file
2024-05-26 08:24:29 -04:00
Pavel P 05f9f83240 Avoid unreferenced formal parameter warning in get_range_offset_and_length (#1838)
Release builds result in the following warning because `content_length` param was used only inside asserts:

1> cpp-httplib\httplib.h(4933,45): warning C4100: 'content_length': unreferenced formal parameter
2024-05-17 09:56:06 +09:00
Jiwoo Park 3b6597bba9 Fix query parsing when value has = characters (#1822)
* Implement string divider to replace splitter

* Divide query string in half

* Add a test case for query values containing the '=' character

* Add test cases for string divider

* Fix warnings
2024-04-21 19:17:14 -04:00
KTGH f10720ed69 Move httplibConf.cmake.in & install readme/license (#1826)
* Move httplibConfig.cmake.in to cmake dir

Just makes more sense to put it there I suppose.

* Cmake install README & License

Seems to make sense since you might already do this as a package
manager, or an end user might want them anyways.

The locations are just based on standard Linux locations using
GNUInstallDirs, so it should be sane on other machines too.
2024-04-21 19:14:12 -04:00
rndm13 2bc550b2f0 Added progress to POST, PUT, PATCH and DELETE requests (#1821)
* Added progress to POST, PUT, PATCH, DELETE requests

* Added tests for post, put, patch, delete progress/cancellation

* fix accidental infinite recursion in delete

---------

Co-authored-by: rndm <rndm@localhost.localdomain>
2024-04-21 19:13:41 -04:00
Jiwoo Park ce36b8a6e5 Highlight notes using markdown features (#1820) 2024-04-18 10:19:54 -04:00
Kent 560854a961 Apply range header base on response status code (#1806)
* Enable ignoring range header to generate customized response

* Apply range header base on response status code
2024-04-11 23:28:21 -04:00
yhirose 2064462c35 Merge branch 'HerrCai0907-fix' 2024-04-11 22:30:00 -04:00
yhirose 07288888ad Code cleanup 2024-04-11 22:26:30 -04:00
yhirose 34d392cf3d Merge branch 'fix' of github.com:HerrCai0907/cpp-httplib into HerrCai0907-fix 2024-04-11 22:22:58 -04:00
yhirose 825c3fbbb1 Removed excess usage of std::move 2024-04-11 21:21:27 -04:00
Rusty Conover 00bdf73ec6 fix: increase default receive buffer to 16kb (#1814)
Since TLS packets have a maximum size of 16kb it makes
sense to fully accommodate them on reads.

Co-authored-by: Rusty Conover <rusty@Juno.local>
2024-04-09 16:59:07 -04:00
Jiwoo Park f44ab9b3da Fix range parser when parsing too many ranges (#1812)
* Implement range parser without std::regex

* Add test cases for invalid ranges
2024-04-07 10:06:16 -04:00
Andrea Pappacoda a61f2b89be build(meson): generate new test PEMs (#1813)
Follow-up to commits 548dfff0ae and
b8bafbc291
2024-04-07 10:05:07 -04:00
Jiwoo Park b8bafbc291 Generate missing PEMs for CTest (#1811)
* Generate missing PEMs

* Fix typo

* Copy files using simpler command
2024-04-05 13:50:21 -04:00
yhirose 548dfff0ae Fix #1793 2024-03-09 22:26:17 -05:00
Congcong Cai 4dd2f3d03d fix ambiguous for HandlerWithResponse and Handler in set_error_handler
using lambda expression as Handler in set_error_handler will cause ambiguous.
Template forwarding can forward HandlerWithResponse to the correct overloading function
2024-03-07 19:07:39 +08:00
KTGH 6791a8364d FindBrotli cleanup & fixes (#1786)
* Unimportant cleanup of FindBrotli

Some whitespace, removal of an unused var, and add a comment about
the missing version support

* Fix incorrect var in FindBrotli

Not much to say, it was just the wrong one. Not sure how that happened,
maybe a holdover from when I did an overhaul at some point..

* Tiny useless edit to FindBrotli

Just a little tweak to how I was postfixing those -static strings to the
lib names. Mostly pointless..

* Simplify some FindBrotli code

Nothing much, just reducing redundant stuff & a cleanup of a comment.

* Ignore PkgConf in FindBrotli if looking for static

As per the issue mentioned in this comment, static PkgConf support
is broken upstream. After testing, I found it'll just accept shared
even if you try to find static, so I'm merely disabling that feature
for this FindBrotli module.

That said, you can still get the static libs if you have them installed,
but PkgConf will be ignored even if found in favor of a regular
find_library call. Otherwise you'd end up with shared libs even if you
ask for static (with PkgConf installed).

TLDR: actually fail correctly when static libs aren't found when PkgConf
thought they were.
2024-02-27 19:25:02 -05:00
KTGH d1a1c8a158 Add description & URL to Cmake (#1785)
Doesn't do much, but some packages/builders might find a use for these
vars it provides...
2024-02-27 19:22:44 -05:00
yhirose b4d26badf2 Update github actions 2024-02-17 09:17:51 -05:00
Jiwoo Park c5a0673c93 Use final keyword for devirtualization (#1779) 2024-02-17 09:17:15 -05:00
Sergey ad40bd6a00 Implement file mapping for UWP apps (#1775)
Fixes #1773.
2024-02-08 22:59:34 -05:00
yhirose 5c00bbf36b Release v0.15.3 2024-02-05 22:12:43 -05:00
yhirose 9d6f5372a3 Fix #1772 2024-02-05 22:11:53 -05:00
Ikko Eltociear Ashimine f06fd934f6 Fix typo in gtest-all.cc (#1770)
synthetic -> synthetic
2024-02-05 15:35:33 -05:00
yhirose 80c0cc445e Release v0.15.2 2024-02-02 23:29:30 -05:00
yhirose 762024b890 Fix #1768 2024-02-02 23:17:32 -05:00
yhirose 82a90a2325 Update year 2024-01-29 08:53:01 -05:00
yhirose b7cac4f4b8 Release v0.15.1 2024-01-29 07:40:56 -05:00
yhirose e323374d2a Fix #1766 2024-01-28 17:43:51 -05:00
Jiwoo Park ffc294d37e Reduce object copy (#1767) 2024-01-28 08:18:29 -05:00
yhirose fceada9ef4 Changed to return 416 for a request with an invalid range 2024-01-28 08:13:19 -05:00
yhirose 5f0f73fad9 Reduce duplicate computation for ranges 2024-01-27 19:07:52 -05:00
11 changed files with 1405 additions and 345 deletions
+24 -22
View File
@@ -3,39 +3,41 @@ name: test
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
ubuntu:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: install brotli
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev
- name: build and run tests
run: cd test && make -j4
- name: run fuzz test target
run: cd test && make fuzz_test
strategy:
matrix:
os: [macOS-latest, ubuntu-latest, windows-latest]
macos:
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: build and run tests
run: |
cd test && make -j2
windows:
runs-on: windows-latest
steps:
- name: prepare git for checkout on windows
if: matrix.os == 'windows-latest'
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: checkout
uses: actions/checkout@v3
- name: install brotli library on ubuntu
if: matrix.os == 'ubuntu-latest'
run: sudo apt update && sudo apt-get install -y libbrotli-dev
- name: install brotli library on macOS
if: matrix.os == 'macOS-latest'
run: brew install brotli
- name: make
if: matrix.os != 'windows-latest'
run: cd test && make -j2
- name: check fuzz test target
if: matrix.os == 'ubuntu-latest'
run: cd test && make fuzz_test
uses: actions/checkout@v4
- name: setup msbuild on windows
if: matrix.os == 'windows-latest'
uses: microsoft/setup-msbuild@v1.1
uses: microsoft/setup-msbuild@v2
- name: make-windows
if: matrix.os == 'windows-latest'
run: |
cd test
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
x64\Release\test.exe
+13 -2
View File
@@ -72,7 +72,12 @@ file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\
# 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)
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.
@@ -189,6 +194,7 @@ if(HTTPLIB_COMPILE)
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
OUTPUT_NAME cpp-httplib
)
else()
# This is for header-only.
@@ -240,7 +246,7 @@ 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("${PROJECT_NAME}Config.cmake.in"
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
@@ -288,6 +294,11 @@ if(HTTPLIB_INSTALL)
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}")
endif()
if(HTTPLIB_TEST)
+27 -14
View File
@@ -7,7 +7,8 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include the **httplib.h** file in your code!
NOTE: 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.
> [!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
---------------
@@ -53,9 +54,11 @@ 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.
> [!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.
NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
> [!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
@@ -76,7 +79,8 @@ cli.set_ca_cert_path("./ca-bundle.crt");
cli.enable_server_certificate_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.
> [!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
------
@@ -190,7 +194,8 @@ The followings are built-in mappings:
| webm | video/webm | zip | application/zip |
| mp3 | audio/mp3 | wasm | application/wasm |
NOTE: These static file server methods are not thread-safe.
> [!WARNING]
> These static file server methods are not thread-safe.
### File request handler
@@ -239,7 +244,8 @@ svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep)
});
```
NOTE: 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!
> [!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
@@ -417,7 +423,8 @@ svr.set_idle_interval(0, 100000); // 100 milliseconds
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`.
> [!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
@@ -496,7 +503,8 @@ int main(void)
}
```
NOTE: Constructor with scheme-host-port string is now supported!
> [!TIP]
> Constructor with scheme-host-port string is now supported!
```c++
httplib::Client cli("localhost");
@@ -704,7 +712,8 @@ cli.set_digest_auth("user", "pass");
cli.set_bearer_token_auth("token");
```
NOTE: OpenSSL is required for Digest Authentication.
> [!NOTE]
> OpenSSL is required for Digest Authentication.
### Proxy server support
@@ -721,7 +730,8 @@ cli.set_proxy_digest_auth("user", "pass");
cli.set_proxy_bearer_token_auth("pass");
```
NOTE: OpenSSL is required for Digest Authentication.
> [!NOTE]
> OpenSSL is required for Digest Authentication.
### Range
@@ -770,7 +780,8 @@ res->status; // 200
### 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
@@ -859,14 +870,16 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
#include <httplib.h>
```
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]
> 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 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
> [!NOTE]
> Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
License
-------
MIT license (© 2023 Yuji Hirose)
MIT license (© 2024 Yuji Hirose)
Special Thanks To
-----------------
+18 -28
View File
@@ -1,6 +1,6 @@
# 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.
#
@@ -14,7 +14,7 @@
# 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.
# But currently there's a version header that doesn't seem to get installed.
# 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
@@ -68,29 +68,24 @@ if(BROTLI_USE_STATIC_LIBS)
set(_brotli_stat_str "_STATIC")
endif()
# Lets us know we are using the PkgConfig libraries
# Will be set false if any non-pkgconf vars are used
set(_brotli_using_pkgconf TRUE)
# 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)
if(PKG_CONFIG_FOUND)
# 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.
if(BROTLI_USE_STATIC_LIBS)
# Have to use _STATIC to tell PkgConfig to find the static libs.
pkg_check_modules(Brotli_${_component_name}_STATIC QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
else()
pkg_check_modules(Brotli_${_component_name} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
endif()
# 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} OR TARGET PkgConfig::Brotli_${_component_name}_STATIC)
# Can't use generators for ALIAS targets, so you get this jank
# 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
@@ -109,23 +104,18 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
continue()
endif()
# Lets us know we aren't using the PkgConfig libraries
set(_brotli_using_pkgconf FALSE)
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)
list(APPEND _brotli_lib_names
"brotli${_libname}-static"
"libbrotli${_libname}-static"
)
else()
list(APPEND _brotli_lib_names
"brotli${_libname}"
"libbrotli${_libname}"
)
# 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}
@@ -168,7 +158,7 @@ find_package_handle_standard_args(Brotli
Brotli_FOUND
REQUIRED_VARS
Brotli_INCLUDE_DIR
${_brotli_required_targets}
${_brotli_req_vars}
HANDLE_COMPONENTS
)
+642 -221
View File
File diff suppressed because it is too large Load Diff
+17 -9
View File
@@ -2,7 +2,7 @@ find_package(GTest)
if(GTest_FOUND)
if(NOT TARGET GTest::gtest_main AND TARGET GTest::Main)
# CMake <3.20
# CMake <3.20
add_library(GTest::gtest_main INTERFACE IMPORTED)
target_link_libraries(GTest::gtest_main INTERFACE GTest::Main)
endif()
@@ -29,14 +29,9 @@ target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/b
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
gtest_discover_tests(httplib-test)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
file(
COPY www www2 www3 ca-bundle.crt image.jpg
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
)
if(HTTPLIB_IS_USING_OPENSSL)
@@ -101,6 +96,19 @@ if(HTTPLIB_IS_USING_OPENSSL)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa -aes256 -passout pass:test012! 2048
OUTPUT_FILE client_encrypted.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client_encrypted.key.pem -passin pass:test012!
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
OUTPUT_FILE client_encrypted.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
add_subdirectory(fuzzing)
+3 -2
View File
@@ -1,8 +1,7 @@
CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
PREFIX = /usr/local
#PREFIX = $(shell brew --prefix)
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
@@ -73,6 +72,8 @@ cert.pem:
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
#c_rehash .
clean:
+1 -1
View File
@@ -1912,7 +1912,7 @@ void AssertHelper::operator=(const Message& message) const {
namespace {
// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P
// to creates test cases for it, a syntetic test case is
// to creates test cases for it, a synthetic test case is
// inserted to report ether an error or a log message.
//
// This configuration bit will likely be removed at some point.
+23 -1
View File
@@ -79,6 +79,26 @@ client_cert_pem = custom_target(
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@']
)
client_encrypted_key_pem = custom_target(
'client_encrypted_key_pem',
output: 'client_encrypted.key.pem',
command: [openssl, 'genrsa', '-aes256', '-passout', 'pass:test012!', '-out', '@OUTPUT@', '2048']
)
client_encrypted_temp_req = custom_target(
'client_encrypted_temp_req',
input: client_encrypted_key_pem,
output: 'client_encrypted_temp_req',
command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-passin', 'pass:test012!', '-out', '@OUTPUT@']
)
client_encrypted_cert_pem = custom_target(
'client_encrypted_cert_pem',
input: [client_encrypted_temp_req, rootca_cert_pem, rootca_key_pem],
output: 'client_encrypted.cert.pem',
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@']
)
# Copy test files to the build directory
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
@@ -112,7 +132,9 @@ test(
rootca_key_pem,
rootca_cert_pem,
client_key_pem,
client_cert_pem
client_cert_pem,
client_encrypted_key_pem,
client_encrypted_cert_pem
],
workdir: meson.current_build_dir(),
timeout: 300
+637 -45
View File
@@ -20,6 +20,9 @@
#define CLIENT_CA_CERT_DIR "."
#define CLIENT_CERT_FILE "./client.cert.pem"
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
#define CLIENT_ENCRYPTED_CERT_FILE "./client_encrypted.cert.pem"
#define CLIENT_ENCRYPTED_PRIVATE_KEY_FILE "./client_encrypted.key.pem"
#define CLIENT_ENCRYPTED_PRIVATE_KEY_PASS "test012!"
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
@@ -113,6 +116,76 @@ TEST(TrimTests, TrimStringTests) {
EXPECT_TRUE(detail::trim_copy("").empty());
}
TEST(DivideTest, DivideStringTests) {
auto divide = [](const std::string &str, char d) {
std::string lhs;
std::string rhs;
detail::divide(str, d,
[&](const char *lhs_data, std::size_t lhs_size,
const char *rhs_data, std::size_t rhs_size) {
lhs.assign(lhs_data, lhs_size);
rhs.assign(rhs_data, rhs_size);
});
return std::make_pair(std::move(lhs), std::move(rhs));
};
{
const auto res = divide("", '=');
EXPECT_EQ(res.first, "");
EXPECT_EQ(res.second, "");
}
{
const auto res = divide("=", '=');
EXPECT_EQ(res.first, "");
EXPECT_EQ(res.second, "");
}
{
const auto res = divide(" ", '=');
EXPECT_EQ(res.first, " ");
EXPECT_EQ(res.second, "");
}
{
const auto res = divide("a", '=');
EXPECT_EQ(res.first, "a");
EXPECT_EQ(res.second, "");
}
{
const auto res = divide("a=", '=');
EXPECT_EQ(res.first, "a");
EXPECT_EQ(res.second, "");
}
{
const auto res = divide("=b", '=');
EXPECT_EQ(res.first, "");
EXPECT_EQ(res.second, "b");
}
{
const auto res = divide("a=b", '=');
EXPECT_EQ(res.first, "a");
EXPECT_EQ(res.second, "b");
}
{
const auto res = divide("a=b=", '=');
EXPECT_EQ(res.first, "a");
EXPECT_EQ(res.second, "b=");
}
{
const auto res = divide("a=b=c", '=');
EXPECT_EQ(res.first, "a");
EXPECT_EQ(res.second, "b=c");
}
}
TEST(SplitTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
Params dic;
@@ -153,14 +226,28 @@ TEST(SplitTest, ParseInvalidQueryTests) {
}
TEST(ParseQueryTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
Params dic;
{
std::string s = "key1=val1&key2=val2&key3=val3";
Params dic;
detail::parse_query_text(s, dic);
detail::parse_query_text(s, dic);
EXPECT_EQ("val1", dic.find("key1")->second);
EXPECT_EQ("val2", dic.find("key2")->second);
EXPECT_EQ("val3", dic.find("key3")->second);
EXPECT_EQ("val1", dic.find("key1")->second);
EXPECT_EQ("val2", dic.find("key2")->second);
EXPECT_EQ("val3", dic.find("key3")->second);
}
{
std::string s = "key1&key2=val1&key3=val1=val2&key4=val1=val2=val3";
Params dic;
detail::parse_query_text(s, dic);
EXPECT_EQ("", dic.find("key1")->second);
EXPECT_EQ("val1", dic.find("key2")->second);
EXPECT_EQ("val1=val2", dic.find("key3")->second);
EXPECT_EQ("val1=val2=val3", dic.find("key4")->second);
}
}
TEST(ParamsToQueryTest, ConvertParamsToQuery) {
@@ -349,6 +436,25 @@ TEST(ParseHeaderValueTest, Range) {
EXPECT_EQ(300u, ranges[2].first);
EXPECT_EQ(400u, ranges[2].second);
}
{
Ranges ranges;
EXPECT_FALSE(detail::parse_range_header("bytes", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=0", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=-", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes= ", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=,", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=,,", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=,,,", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=a-b", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=1-0", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=0--1", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=0- 1", ranges));
EXPECT_FALSE(detail::parse_range_header("bytes=0 -1", ranges));
EXPECT_TRUE(ranges.empty());
}
}
TEST(ParseAcceptEncoding1, AcceptEncoding) {
@@ -757,6 +863,322 @@ TEST(CancelTest, WithCancelLargePayload_Online) {
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, NoCancelPost) {
Server svr;
svr.Post("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(CancelTest, WithCancelSmallPayloadPost) {
Server svr;
svr.Post("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayloadPost) {
Server svr;
svr.Post("/", [&](const Request & /*req*/, Response &res) {
res.set_content(LARGE_DATA, "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, NoCancelPut) {
Server svr;
svr.Put("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(CancelTest, WithCancelSmallPayloadPut) {
Server svr;
svr.Put("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayloadPut) {
Server svr;
svr.Put("/", [&](const Request & /*req*/, Response &res) {
res.set_content(LARGE_DATA, "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, NoCancelPatch) {
Server svr;
svr.Patch("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(CancelTest, WithCancelSmallPayloadPatch) {
Server svr;
svr.Patch("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayloadPatch) {
Server svr;
svr.Patch("/", [&](const Request & /*req*/, Response &res) {
res.set_content(LARGE_DATA, "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, NoCancelDelete) {
Server svr;
svr.Delete("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(CancelTest, WithCancelSmallPayloadDelete) {
Server svr;
svr.Delete("/", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayloadDelete) {
Server svr;
svr.Delete("/", [&](const Request & /*req*/, Response &res) {
res.set_content(LARGE_DATA, "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res =
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(BaseAuthTest, FromHTTPWatch_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
@@ -1325,6 +1747,64 @@ TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
X509* readCertificate (const std::string& strFileName) {
std::ifstream inStream (strFileName);
std::string strCertPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
if (strCertPEM.empty ()) return (nullptr);
BIO* pbCert = BIO_new (BIO_s_mem ());
BIO_write (pbCert, strCertPEM.c_str (), (int)strCertPEM.size ());
X509* pCert = PEM_read_bio_X509 (pbCert, NULL, 0, NULL);
BIO_free (pbCert);
return (pCert);
}
EVP_PKEY* readPrivateKey (const std::string& strFileName) {
std::ifstream inStream (strFileName);
std::string strPrivateKeyPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
if (strPrivateKeyPEM.empty ()) return (nullptr);
BIO* pbPrivKey = BIO_new (BIO_s_mem ());
BIO_write (pbPrivKey, strPrivateKeyPEM.c_str (), (int) strPrivateKeyPEM.size ());
EVP_PKEY* pPrivateKey = PEM_read_bio_PrivateKey (pbPrivKey, NULL, NULL, NULL);
BIO_free (pbPrivKey);
return (pPrivateKey);
}
TEST(BindServerTest, UpdateCerts) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE);
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
X509* cert = readCertificate (SERVER_CERT_FILE);
X509* ca_cert = readCertificate (CLIENT_CA_CERT_FILE);
EVP_PKEY* key = readPrivateKey (SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(cert != nullptr);
ASSERT_TRUE(ca_cert != nullptr);
ASSERT_TRUE(key != nullptr);
X509_STORE* cert_store = X509_STORE_new ();
X509_STORE_add_cert (cert_store, ca_cert);
svr.update_certs (cert, key, cert_store);
ASSERT_TRUE(svr.is_valid());
svr.stop();
X509_free (cert);
X509_free (ca_cert);
EVP_PKEY_free (key);
}
#endif
TEST(ErrorHandlerTest, ContentLength) {
Server svr;
@@ -1831,7 +2311,7 @@ protected:
});
})
.Get("/streamed-with-range",
[&](const Request & /*req*/, Response &res) {
[&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), "text/plain",
@@ -1845,8 +2325,8 @@ protected:
EXPECT_TRUE(ret);
return true;
},
[data](bool success) {
EXPECT_TRUE(success);
[data, &req](bool success) {
EXPECT_EQ(success, !req.has_param("error"));
delete data;
});
})
@@ -1868,6 +2348,11 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content("abcdefg", "text/plain");
})
.Get("/with-range-customized-response",
[&](const Request & /*req*/, Response &res) {
res.status = StatusCode::BadRequest_400;
res.set_content(JSON_DATA, "application/json");
})
.Post("/chunked",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(req.body, "dechunked post body");
@@ -2166,6 +2651,11 @@ protected:
EXPECT_EQ("4", req.get_header_value("Content-Length"));
res.set_content(req.body, "application/octet-stream");
})
.Get("/issue1772",
[&](const Request & /*req*/, Response &res) {
res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=123456");
})
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
.Get("/compress",
[&](const Request & /*req*/, Response &res) {
@@ -2509,6 +2999,12 @@ TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, GetMethodOutOfBaseDirMountWithBackslash) {
auto res = cli_.Get("/mount/%2e%2e%5c/www2/dir/test.html");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, PostMethod303) {
auto res = cli_.Post("/1", "body", "text/plain");
ASSERT_TRUE(res);
@@ -2957,13 +3453,12 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-9999"}});
auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetStreamedWithRangeError) {
@@ -2972,16 +3467,18 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
"92233720368547758079223372036854775807"}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res =
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
@@ -2994,6 +3491,41 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
EXPECT_EQ(267U, res->body.size());
}
TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
Ranges ranges;
for (size_t i = 0; i < CPPHTTPLIB_RANGE_MAX_COUNT + 1; i++) {
ranges.emplace_back(0, -1);
}
auto res =
cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
auto res = cli_.Get("/streamed-with-range?error",
{{make_range_header({{0, -1}, {0, -1}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) {
auto res = cli_.Get("/streamed-with-range?error",
{{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
uint64_t offset = 0;
auto res = cli_.Get("/streamed-cancel",
@@ -3097,6 +3629,30 @@ TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
}
TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(JSON_DATA, res->body);
}
TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(JSON_DATA, res->body);
}
TEST_F(ServerTest, Issue1772) {
auto res = cli_.Get("/issue1772", {{make_range_header({{1000, -1}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res);
@@ -5056,15 +5612,16 @@ TEST(SSLClientTest, SetInterfaceWithINET6) {
}
#endif
TEST(SSLClientServerTest, ClientCertPresent) {
void ClientCertPresent(
const std::string &client_cert_file,
const std::string &client_private_key_file,
const std::string &client_encrypted_private_key_pass = std::string()) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &req, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
auto peer_cert = SSL_get_peer_certificate(req.ssl);
ASSERT_TRUE(peer_cert != nullptr);
@@ -5087,13 +5644,15 @@ TEST(SSLClientServerTest, ClientCertPresent) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
SSLClient cli(HOST, PORT, client_cert_file, client_private_key_file,
client_encrypted_private_key_pass);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(30);
@@ -5102,35 +5661,43 @@ TEST(SSLClientServerTest, ClientCertPresent) {
ASSERT_EQ(StatusCode::OK_200, res->status);
}
#if !defined(_WIN32) || defined(OPENSSL_USE_APPLINK)
TEST(SSLClientServerTest, MemoryClientCertPresent) {
X509 *server_cert;
EVP_PKEY *server_private_key;
X509_STORE *client_ca_cert_store;
X509 *client_cert;
EVP_PKEY *client_private_key;
TEST(SSLClientServerTest, ClientCertPresent) {
ClientCertPresent(CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
}
FILE *f = fopen(SERVER_CERT_FILE, "r+");
server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
TEST(SSLClientServerTest, ClientEncryptedCertPresent) {
ClientCertPresent(CLIENT_ENCRYPTED_CERT_FILE,
CLIENT_ENCRYPTED_PRIVATE_KEY_FILE,
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
}
#if !defined(_WIN32) || defined(OPENSSL_USE_APPLINK)
void MemoryClientCertPresent(
const std::string &client_cert_file,
const std::string &client_private_key_file,
const std::string &client_encrypted_private_key_pass = std::string()) {
auto f = fopen(SERVER_CERT_FILE, "r+");
auto server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(SERVER_PRIVATE_KEY_FILE, "r+");
server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
auto server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(CLIENT_CA_CERT_FILE, "r+");
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
client_ca_cert_store = X509_STORE_new();
auto client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
auto client_ca_cert_store = X509_STORE_new();
X509_STORE_add_cert(client_ca_cert_store, client_cert);
X509_free(client_cert);
fclose(f);
f = fopen(CLIENT_CERT_FILE, "r+");
f = fopen(client_cert_file.c_str(), "r+");
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(CLIENT_PRIVATE_KEY_FILE, "r+");
client_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
f = fopen(client_private_key_file.c_str(), "r+");
auto client_private_key = PEM_read_PrivateKey(
f, nullptr, nullptr, (void *)client_encrypted_private_key_pass.c_str());
fclose(f);
SSLServer svr(server_cert, server_private_key, client_ca_cert_store);
@@ -5138,8 +5705,6 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
svr.Get("/test", [&](const Request &req, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
auto peer_cert = SSL_get_peer_certificate(req.ssl);
ASSERT_TRUE(peer_cert != nullptr);
@@ -5162,13 +5727,15 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
SSLClient cli(HOST, PORT, client_cert, client_private_key);
SSLClient cli(HOST, PORT, client_cert, client_private_key,
client_encrypted_private_key_pass);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(30);
@@ -5181,6 +5748,16 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
X509_free(client_cert);
EVP_PKEY_free(client_private_key);
}
TEST(SSLClientServerTest, MemoryClientCertPresent) {
MemoryClientCertPresent(CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
}
TEST(SSLClientServerTest, MemoryClientEncryptedCertPresent) {
MemoryClientCertPresent(CLIENT_ENCRYPTED_CERT_FILE,
CLIENT_ENCRYPTED_PRIVATE_KEY_FILE,
CLIENT_ENCRYPTED_PRIVATE_KEY_PASS);
}
#endif
TEST(SSLClientServerTest, ClientCertMissing) {
@@ -5212,11 +5789,11 @@ TEST(SSLClientServerTest, TrustDirOptional) {
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
@@ -5308,13 +5885,12 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
nullptr);
return true;
};
SSLServer svr(setup_ssl_ctx_callback);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &req, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
auto peer_cert = SSL_get_peer_certificate(req.ssl);
ASSERT_TRUE(peer_cert != nullptr);
@@ -5337,6 +5913,7 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
@@ -6854,3 +7431,18 @@ TEST(PathParamsTest, SequenceOfParams) {
EXPECT_EQ(request.path_params, expected_params);
}
TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
// If ipv6 regex working, regex match codepath is taken.
// else port will default to 80 in Client impl
int clientImplMagicPort = 80;
int port = 4321;
// above ports must be different to avoid false negative
EXPECT_NE(clientImplMagicPort, port);
std::string ipV6TestURL = "http://[ff06::c3]";
Client cli(ipV6TestURL + ":" + std::to_string(port), CLIENT_CERT_FILE,
CLIENT_PRIVATE_KEY_FILE);
EXPECT_EQ(cli.port(), port);
}