Compare commits

...

89 Commits

Author SHA1 Message Date
yhirose 9452c0a4b6 Release v0.10.7 2022-04-28 10:21:14 -04:00
Yoshiki Matsuda 307b729549 Accept large data transfer over SSL (#1261)
* Add large data transfer test

* Replace `SSL_read` and `SSL_write` with `ex` functions

* Reflect review comment

* Fix return value of `SSLSocketStream::read/write`

* Fix return value in the case of `SSL_ERROR_ZERO_RETURN`

* Disable `LargeDataTransfer` test due to OoM in CI
2022-04-27 21:08:39 -04:00
mylogin 696239d6e1 Link Windows crypto libs only when CPPHTTPLIB_OPENSSL_SUPPORT is set (#1254) 2022-04-20 22:04:55 -04:00
Andrea Pappacoda 6929d90353 build(meson): allow using OpenSSL 3.0 (#1256)
Following 0857eba17b cpp-httplib is fully compatible with OpenSSL versions newer than 1.1.1
2022-04-20 21:39:52 -04:00
yhirose 348d032029 Updated README 2022-04-19 23:02:30 -04:00
Andrea Pappacoda d1d3fcdfd5 build(meson): mark *_encrypted_pem as test deps (#1255)
Meson only runs required targets. The key_encrypted_pem and
cert_encrypted_pem targets added in 020b0db090
and 8191fd8e6c weren't added to the list
of targets required by the test target, so the generation of the
encrypted certs was skipped, resulting in the failure of
BindServerTest.BindAndListenSeparatelySSLEncryptedKey.
2022-04-19 07:12:00 -04:00
Eli Schwartz abf3a67dd0 meson: fix regression that broke extracting version (#1253)
* meson: fix regression that broke extracting version

In commit 33f67386fe the code that
heuristically parsed the version broke due to the version being moved
around into a more easily accessible define.

While we are at it, pass the exact path of httplib.h to un-break usage
as a meson subproject. This was broken in commit
8ecdb11979 which checked the return code
of trying to get the version; it was always broken, but formerly failed
in silence and resulted in no version number.

* meson: use the compiler builtins to extract the version from the header

As a convenient string define, it is now possible to ask the
preprocessor what the version of cpp-httplib is. This can be used from
meson too, in order to avoid encoding C++ file structure into python
regexes.
2022-04-19 07:11:51 -04:00
yhirose d87abeecf0 Release v0.10.6 2022-04-17 17:34:48 -04:00
yhirose 4e28e4f741 Fix #1251 2022-04-17 11:53:41 -04:00
yhirose 80a55cedeb Removed Repl.it examples 2022-04-16 21:11:17 -04:00
yhirose d05c343602 Release v0.10.5 2022-04-16 21:02:30 -04:00
yhirose 33f67386fe Fix #1249 2022-04-16 08:54:56 -04:00
yhirose 56d8168dc4 clangformat 2022-04-16 08:52:55 -04:00
greenfish 5d87cc0558 resolve compiler warnings (#1246)
* resolve compiler warnings

- check `WSAStartup` return.
- `const` is not suitable for `std::move`.

* resolve compiler warnings

- bool startup => bool is_valid_.
- remove `const` not removed.
2022-04-14 11:46:10 -04:00
yhirose cb41947eb4 Fix #1235 (#1243)
* Fix #1235

* fix BindIPAddress error (#1242)

* Code cleanup

* Added a unit test

* Commented out 'SSLClientTest.SetInterfaceWithINET6'

* Fixed incorrect return value from if2ip

* Removed if_nametoindex call

Co-authored-by: Kotarou <2918558+CyberKoo@users.noreply.github.com>
2022-04-13 21:32:46 -04:00
Kotarou 0857eba17b replace deprecated OpenSSL functions with evp functions (#1241) 2022-04-11 13:40:58 -04:00
Andrea Pappacoda 020b0db090 build(meson): generate key_encrypted.pem (#1221)
8191fd8e6c only added one of the two files
2022-03-20 12:21:45 -04:00
Andrea Pappacoda bf0760fde4 fix: update user agent (#1218) 2022-03-18 18:12:51 -04:00
yhirose bb8e45383e Update README 2022-03-17 08:38:15 -04:00
yhirose a1df576e4f Fix #1212 2022-03-16 22:00:40 -04:00
yhirose 7fb0254794 Fix #1215 2022-03-16 12:50:13 -04:00
yhirose c82d1e52cc Fix #1214 2022-03-16 10:56:33 -04:00
yhirose 846151b605 Added a unit test case for large multipart form data 2022-03-16 09:37:13 -04:00
Alexandre Bouvier e44e31dd5b Add soversion (#1209) 2022-03-03 21:06:08 -05:00
yhirose f7b9501662 clangformat 2022-02-27 14:31:22 -05:00
yhirose e12fe4cbbb Performance improvement 2022-02-27 14:30:49 -05:00
yhirose 49d2e1f135 Fix problem with InvalidPort test 2022-02-27 14:29:34 -05:00
Sebastien Blanchet 8191fd8e6c Add optional private key password to SSLServer ctor (#1205) 2022-02-27 14:16:15 -05:00
yhirose d73395e1dc Release v0.10.3 2022-02-22 10:21:27 -05:00
au-ee 64d001162b CPPHTTPLIB_NO_DEFAULT_USER_AGENT skips default user agent (#1201) 2022-02-22 09:39:26 -05:00
yhirose bb00a23116 Apply clangformat 2022-02-09 17:16:47 -05:00
yhirose 63d6e9b91b Removed up.sh and down.sh 2022-02-09 17:16:18 -05:00
Edwin Kofler 66eed5681a Fix typo in Dockerfile (#1187) 2022-02-06 09:15:15 -05:00
Andrea Pappacoda 8ecdb11979 build(meson): always install a pkg-config file (#1182)
A pkg-config file was previously installed only if cpp-httplib was being
built as a compiled library.

Since architecture-independent .pc files
can exist in /usr/share/pkgconfig, it can be useful to install one even
when installing the header-only version (for example, it could be used
by third party projects to easily find out if cpp-httplib is installed
and its version, using something like Meson's `dependency()` or CMake's
`pkg_check_modules()`).

The change makes the Meson build behave a bit more like the CMake one,
as it also always installs a CMake Config file, but here the pkg-config
file gets installed to the correct architecture-independent directory
(`datadir` represents /usr/share on Linux and simiar systems).

Lastly, I made some minor cleanups.
2022-02-03 19:50:49 -05:00
Andrea Pappacoda 894fcc8e02 test: add missing "_Online" suffix (#1183)
This test fails reproducibly in a Debian build chroot, and they generally don't have internet access
2022-01-30 12:34:52 -05:00
Rockybilly 7f43f0f3ff User-Agent update cpp-httplib/0.10.2 (#1181) 2022-01-28 12:27:27 -05:00
Gregor Jasny 87e03dd1ce Report connection timeout as separate event (#1171) 2022-01-26 17:32:40 -05:00
yhirose e5cacb465d Fix #1172 (#1173)
This change is based on RFC7230, § 3.5 'Message Parsing Robustness': "Although the line terminator for the start-line and header fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR."
2022-01-26 13:34:23 -05:00
yhirose ee8371f753 Added 'PostLarge' unit test for #1169 2022-01-22 09:52:27 -05:00
yhirose 081723f983 Add another fuzz test corpus 2022-01-20 15:27:26 -05:00
yhirose b61f36579c Fix #1166 2022-01-20 15:24:09 -05:00
ArnaudBienner 33f53aa458 Fix set_content_provider example in README.md (#1163) 2022-01-13 12:26:34 -05:00
yhirose 412ab5f063 Added example/Dockerfile.hello 2022-01-11 00:18:20 -05:00
yhirose 11e02e901c Fixed unit test 2021-12-31 16:10:57 -05:00
yhirose 65a8f4cf44 Added hosted_at. (Resolve #1113) 2021-12-31 15:35:52 -05:00
yhirose 27d128bbb4 Fix problems in #1154 2021-12-31 14:55:40 -05:00
yhirose 070f9bec58 Code cleanup 2021-12-31 13:27:47 -05:00
c00c f817032513 fix socket option setting for windows (#1154)
* fix socket option setting for windows

* misc

Co-authored-by: zhangsen <zhangsen@cyberkl.com>
2021-12-31 10:07:59 -05:00
yhirose 17abe221c0 Fix is_file problem on Windows (#1153) 2021-12-30 14:54:57 -05:00
vitaly-ivanov 4a7a81e039 Work around silly Win defines to support BoringSSL (#1148)
* Work around silly Win defines to support BoringSSL

* changes wrapped into ifdef(_WIN32) just in case
2021-12-30 11:08:51 -05:00
yhirose 37fd4eb643 Code cleanup 2021-12-26 07:53:56 -05:00
yhirose 865b0e4c03 Resolve #1145 2021-12-26 07:53:06 -05:00
yhirose b324921c1a Release v0.10.1 2021-12-24 21:01:06 -05:00
yhirose 63f72caf30 Fix "Issue 42689 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-12-24 20:58:09 -05:00
yhirose 99ac17b90a Fix #1140 2021-12-23 23:19:14 -05:00
yhirose 4b0ed9ee88 Release v0.10.0 2021-12-21 18:15:41 -05:00
yhirose 20056f6cda Update test.cc 2021-12-19 14:19:10 -05:00
yhirose 3b35279b16 Added SSLServer::ssl_context() 2021-12-18 00:21:41 -05:00
yhirose 27deb44df5 Update SSL related code 2021-12-18 00:15:38 -05:00
yhirose 24a3ef949b Performance improvement for multipart form data file upload. 2021-12-18 00:15:10 -05:00
yhirose bc3e098964 Updated .gitignore 2021-12-17 22:36:02 -05:00
yhirose c247dcdd7b Added uploader.sh 2021-12-17 22:34:00 -05:00
yhirose 793ae9855e Fix #1041 (#1132)
* Fix #1041

* Fixed problem with is_socket_alive

* Adjust the way to check if the sockt is still alive.

* Revert "Adjust the way to check if the sockt is still alive."

This reverts commit 6c673b21e5.

* Adjust is_socket_alive according to the code review
2021-12-16 21:06:17 -05:00
yhirose 9fa426d51b Added more fuzzing corpus 2021-12-14 18:35:20 -05:00
yhirose cec6288a99 Resolve #1131 2021-12-14 07:58:21 -05:00
yhirose 9639578c2a Release v0.9.10 2021-12-11 19:26:22 -05:00
yhirose 743ecbd365 Issue1121 (#1122)
* Fixed test/Makefile problem when cleaning *.pem files

* Fix #1121
2021-12-11 19:07:12 -05:00
yhirose 084c643973 Fixed README 2021-12-10 22:33:59 -05:00
Andrea Pappacoda 824e7682e4 test: add missing _Online suffixes (#1110) 2021-11-27 09:54:05 -05:00
Andrea Pappacoda f9074684dd build(meson): drop Git-based version detection (#1109)
See 3051152103
2021-11-27 09:47:09 -05:00
yhirose ddff782133 Release v0.9.9 2021-11-26 20:46:38 -05:00
yhirose 3051152103 Fix #1102 (#1108) 2021-11-26 20:44:58 -05:00
yhirose 06026bb47d Code formating 2021-11-23 10:53:05 -05:00
yhirose 226388ae27 Resolve #1100 2021-11-23 10:47:30 -05:00
Scott Graham ea7548b4cc Remove stray ; causing warning with -Wextra-semi (#1099) 2021-11-22 07:16:07 -05:00
Rodolphe c7486ead96 accept protobuf encoding (#1096)
Co-authored-by: rodolphe <rodolphe@zen.ly>
2021-11-17 13:14:31 -05:00
yhirose 90a291214c Update Makefile 2021-11-15 23:08:49 -05:00
Andrea Pappacoda c111c42a86 build(meson): feature args in pkg-config file (#1092)
Follow-up for #1090. The args are now also added to the pkg-config file.
2021-11-15 22:50:33 -05:00
yhirose 6fb5b63018 Fix #1093. Remove meson-build tests from GitHubActions (#1094) 2021-11-15 22:49:40 -05:00
yhirose ec56dfa35e Fix #1085 (#1091) 2021-11-15 14:37:10 -05:00
Andrea Pappacoda 943cd51b67 build(meson): pass feature args to dependency consumers (#1090)
Fixes #1087
2021-11-15 14:03:25 -05:00
Yuji Hirose 301faa074c Added test case for #1065 2021-10-29 07:29:23 -04:00
DavidKorczynski dc0481e832 Add CIFuzz integration (#1079)
Signed-off-by: David Korczynski <david@adalogics.com>
2021-10-27 12:19:21 -04:00
zhenyolka 4f8fcdbaf7 Allow to specify server IP address (#1067)
* Allow to specify server IP address

* Reimplement in set_hostname_addr_map

* Add tests for set_hostname_addr_map

* Fix tests after implement set_hostname_addr_map

* SpecifyServerIPAddressTest.RealHostname typo
2021-10-16 15:05:55 -04:00
CarlosLeeGit b80aa7fee3 support custom ssl ctx configuration for SSLServer (#1073) 2021-10-15 07:13:16 -04:00
yhirose c384be02c9 Fixed GitHub Actions build error 2021-10-14 10:52:05 -04:00
yhirose d17ac3bb40 Fix "Issue 39922 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-10-14 08:55:29 -04:00
yhirose c7554ccac2 Fix #1069 (#1070) 2021-10-09 20:35:58 -04:00
yhirose 35ef1c7bae Fix #1063 2021-10-03 18:37:59 -04:00
25 changed files with 1108 additions and 428 deletions
+26
View File
@@ -0,0 +1,26 @@
name: CIFuzz
on: [pull_request]
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@v1
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
+1 -53
View File
@@ -29,7 +29,7 @@ jobs:
run: cd test && make -j2
- name: check fuzz test target
if: matrix.os == 'ubuntu-latest'
run: cd test && make -f Makefile.fuzz_test
run: cd test && make fuzz_test
- name: setup msbuild on windows
if: matrix.os == 'windows-latest'
uses: microsoft/setup-msbuild@v1.0.2
@@ -39,55 +39,3 @@ jobs:
cd test
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
x64\Release\test.exe
meson-build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, 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
- uses: actions/checkout@v2
- name: Install dependencies on Linux
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get -qq update && sudo apt-get -qq install meson libssl-dev zlib1g-dev libbrotli-dev libgtest-dev
- name: Install dependencies on MacOS
if: matrix.os == 'macos-latest'
run: brew install meson openssl brotli googletest
- name: Setup MSVC on Windows
if: matrix.os == 'windows-latest'
uses: ilammy/msvc-dev-cmd@v1
# It is necessary to remove MinGW and StrawberryPerl as they both provide
# GCC. This causes issues because CMake prefers to use MSVC, while Meson
# uses GCC, if found, causing linking errors.
- name: Install dependencies on Windows
if: matrix.os == 'windows-latest'
run: |
choco uninstall mingw strawberryperl --yes --all-versions --remove-dependencies --skip-autouninstaller --no-color
Remove-Item -Path C:\Strawberry -Recurse
choco install pkgconfiglite --yes --skip-virus-check --no-color
pip install meson ninja
Invoke-WebRequest -Uri https://github.com/google/googletest/archive/refs/heads/master.zip -OutFile googletest-master.zip
Expand-Archive -Path googletest-master.zip
cd googletest-master\googletest-master
cmake -S . -B build -DINSTALL_GTEST=ON -DBUILD_GMOCK=OFF -Dgtest_hide_internal_symbols=ON -DCMAKE_INSTALL_PREFIX=C:/googletest
cmake --build build --config=Release
cmake --install build --config=Release
cd ..\..
- name: Build and test
run: |
meson setup build -Dcpp-httplib_test=true -Dpkg_config_path=C:\googletest\lib\pkgconfig -Db_vscrt=static_from_buildtype
meson test --no-stdsplit --print-errorlogs -C build
+2
View File
@@ -13,10 +13,12 @@ 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
+9 -20
View File
@@ -60,27 +60,11 @@
]]
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
# On systems without Git installed, there were errors since execute_process seemed to not throw an error without it?
find_package(Git QUIET)
if(Git_FOUND)
# Gets the latest tag as a string like "v0.6.6"
# Can silently fail if git isn't on the system
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE _raw_version_string
ERROR_VARIABLE _git_tag_error
)
endif()
# 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]+)\"")
# execute_process can fail silenty, so check for an error
# if there was an error, just use the user agent as a version
if(_git_tag_error OR NOT Git_FOUND)
message(WARNING "cpp-httplib failed to find the latest Git tag, falling back to using user agent as the version.")
# Get the user agent and use it as a version
# This gets the string with the user agent from the header.
# This is so the maintainer doesn't actually need to update this manually.
file(STRINGS httplib.h _raw_version_string REGEX "User\-Agent.*cpp\-httplib/([0-9]+\.?)+")
endif()
# Needed since git tags have "v" prefixing them.
# Also used if the fallback to user agent string is being used.
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
@@ -188,6 +172,11 @@ if(HTTPLIB_COMPILE)
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
)
set_target_properties(${PROJECT_NAME}
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
)
else()
# This is for header-only.
set(_INTERFACE_OR_PUBLIC INTERFACE)
+18 -8
View File
@@ -48,17 +48,12 @@ res->status;
res->body;
```
### Try out the examples on Repl.it!
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1.
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
@@ -183,6 +178,15 @@ The followings are built-in mappings:
| webm | video/webm | zip | application/zip |
| mp3 | audio/mp3 | wasm | application/wasm |
### File request handler
```cpp
// The handler is called right before the response is sent to a client
svr.set_file_request_handler([](const Request &req, Response &res) {
...
});
```
NOTE: These static file server methods are not thread-safe.
### Logging
@@ -220,7 +224,7 @@ svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) {
### Pre routing handler
```cpp
svr.set_pre_routing_handler([](const auto& req, auto& res) -> bool {
svr.set_pre_routing_handler([](const auto& req, auto& res) {
if (req.path == "/hello") {
res.set_content("world", "text/html");
return Server::HandlerResponse::Handled;
@@ -303,7 +307,7 @@ Without content length:
svr.Get("/stream", [&](const Request &req, Response &res) {
res.set_content_provider(
"text/plain", // Content type
[&](size_t offset, size_t length, DataSink &sink) {
[&](size_t offset, DataSink &sink) {
if (/* there is still data */) {
std::vector<char> data;
// prepare data...
@@ -746,6 +750,12 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->body; // Compressed data
```
Use `poll` instead of `select`
------------------------------
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
Split httplib.h into .h and .cc
-------------------------------
+12
View File
@@ -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"]
+9 -6
View File
@@ -1,14 +1,17 @@
#CXX = clang++
CXXFLAGS = -std=c++11 -I.. -Wall -Wextra -pthread
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
OPENSSL_DIR = /usr/local/opt/openssl
PREFIX = /usr/local
#PREFIX = $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = /usr/local/opt/brotli
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
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
@@ -47,4 +50,4 @@ pem:
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
clean:
rm server client hello simplecli simplesvr upload redirect ssesvr sselci benchmark *.pem
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
+1 -1
View File
@@ -15,5 +15,5 @@ int main(void) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("localhost", 8080);
svr.listen("0.0.0.0", 8080);
}
+6
View File
@@ -0,0 +1,6 @@
#/usr/bin/env bash
for i in {1..10000}
do
echo "#### $i ####"
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
done
+545 -249
View File
File diff suppressed because it is too large Load Diff
+19 -21
View File
@@ -19,24 +19,13 @@ project(
# Check just in case downstream decides to edit the source
# and add a project version
version = meson.project_version()
python3 = find_program('python3')
if version == 'undefined'
git = find_program('git', required: false)
if git.found()
result = run_command(git, 'describe', '--tags', '--abbrev=0')
if result.returncode() == 0
version = result.stdout().strip('v\n')
endif
endif
endif
python = import('python').find_installation('python3')
# If version is still undefined it means that the git method failed
if version == 'undefined'
# Meson doesn't have regular expressions, but since it is implemented
# in python we can be sure we can use it to parse the file manually
version = run_command(
python, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))'
).stdout().strip()
cxx = meson.get_compiler('cpp')
version = cxx.get_define('CPPHTTPLIB_VERSION',
prefix: '#include <httplib.h>',
include_directories: include_directories('.')).strip('"')
assert(version != '', 'failed to get version from httplib.h')
endif
message('cpp-httplib version ' + version)
@@ -44,7 +33,7 @@ message('cpp-httplib version ' + version)
deps = [dependency('threads')]
args = []
openssl_dep = dependency('openssl', version: ['>=1.1.1', '<1.1.2'], required: get_option('cpp-httplib_openssl'))
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
if openssl_dep.found()
deps += openssl_dep
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
@@ -79,7 +68,7 @@ if get_option('cpp-httplib_compile')
'split',
input: 'httplib.h',
output: ['httplib.cc', 'httplib.h'],
command: [python, files('split.py'), '--out', meson.current_build_dir()],
command: [python3, files('split.py'), '--out', meson.current_build_dir()],
install: true,
install_dir: [false, get_option('includedir')]
)
@@ -91,17 +80,26 @@ if get_option('cpp-httplib_compile')
version: version,
install: true
)
cpp_httplib_dep = declare_dependency(link_with: lib, sources: httplib_ch[1])
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
import('pkgconfig').generate(
lib,
description: 'A C++ HTTP/HTTPS server and client library',
extra_cflags: args,
url: 'https://github.com/yhirose/cpp-httplib',
version: version
)
else
install_headers('httplib.h')
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'), dependencies: deps)
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
import('pkgconfig').generate(
name: 'cpp-httplib',
description: 'A C++ HTTP/HTTPS server and client library',
install_dir: join_paths(get_option('datadir'), 'pkgconfig'),
url: 'https://github.com/yhirose/cpp-httplib',
version: version
)
endif
if meson.version().version_compare('>=0.54.0')
+1 -1
View File
@@ -5,5 +5,5 @@
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires Python 3)')
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file')
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
+35 -7
View File
@@ -1,20 +1,30 @@
#CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
OPENSSL_DIR = /usr/local/opt/openssl@1.1
PREFIX = /usr/local
#PREFIX = $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = /usr/local/opt/brotli
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
# provided via parameters.
# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a"
# to link the fuzzer(s) against a real fuzzing engine.
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
all : test test_split
./test
# Note: The intention of test_split is to verify that it works to compile and
# link the split httplib.h, so there is normally no need to execute it.
proxy : test_proxy
./test_proxy
@@ -22,12 +32,28 @@ proxy : test_proxy
test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS)
# Note: The intention of test_split is to verify that it works to compile and
# link the split httplib.h, so there is normally no need to execute it.
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE).
# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
fuzz_test: server_fuzzer
./server_fuzzer fuzzing/corpus/*
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
# feeds it to server_fuzzer.
standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp
$(CXX) -o $@ -I.. $(CXXFLAGS) -c $<
httplib.cc : ../httplib.h
python3 ../split.py -o .
@@ -39,7 +65,9 @@ cert.pem:
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
openssl genrsa 2048 > client.key.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
#c_rehash .
clean:
rm -f test test_proxy pem *.0 *.1 *.srl httplib.h httplib.cc
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
-36
View File
@@ -1,36 +0,0 @@
#CXX = clang++
CXXFLAGS += -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
OPENSSL_DIR = /usr/local/opt/openssl@1.1
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = /usr/local/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
# provided via parameters.
# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a"
# to link the fuzzer(s) against a real fuzzing engine.
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE).
# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
all fuzz_test: server_fuzzer
./server_fuzzer fuzzing/corpus/*
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
$(CXX) $(CXXFLAGS) -o $@ $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
# feeds it to server_fuzzer.
standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -20,16 +20,16 @@ int main(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
std::ifstream in(argv[i]);
in.seekg(0, in.end);
size_t length = in.tellg();
size_t length = static_cast<size_t>(in.tellg());
in.seekg (0, in.beg);
std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl;
// Allocate exactly length bytes so that we reliably catch buffer overflows.
std::vector<char> bytes(length);
in.read(bytes.data(), bytes.size());
in.read(bytes.data(), static_cast<std::streamsize>(bytes.size()));
LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()),
bytes.size());
std::cout << "Execution successful" << std::endl;
}
std::cout << "Execution finished" << std::endl;
return 0;
}
}
+15
View File
@@ -33,6 +33,19 @@ cert2_pem = custom_target(
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
key_encrypted_pem = custom_target(
'key_encrypted_pem',
output: 'key_encrypted.pem',
command: [openssl, 'genrsa', '-passout', 'pass:test123!', '-out', '@OUTPUT@', '2048']
)
cert_encrypted_pem = custom_target(
'cert_encrypted_pem',
input: key_encrypted_pem,
output: 'cert_encrypted.pem',
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
rootca_key_pem = custom_target(
'rootca_key_pem',
output: 'rootCA.key.pem',
@@ -87,6 +100,8 @@ test(
key_pem,
cert_pem,
cert2_pem,
key_encrypted_pem,
cert_encrypted_pem,
rootca_key_pem,
rootca_cert_pem,
client_key_pem,
-1
View File
@@ -1 +0,0 @@
docker-compose down --rmi all
-1
View File
@@ -1 +0,0 @@
docker-compose up -d
+399 -21
View File
@@ -18,6 +18,9 @@
#define CLIENT_CA_CERT_DIR "."
#define CLIENT_CERT_FILE "./client.cert.pem"
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
using namespace std;
using namespace httplib;
@@ -37,8 +40,12 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
auto it = std::find_if(
files.begin(), files.end(),
[&](const MultipartFormData &file) { return file.name == key; });
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
return *it;
#else
if (it != files.end()) { return *it; }
throw std::runtime_error("invalid mulitpart form data name error");
#endif
}
TEST(ConstructorTest, MoveConstructible) {
@@ -54,6 +61,14 @@ TEST(StartupTest, WSAStartup) {
}
#endif
TEST(DecodeURLTest, PercentCharacter) {
EXPECT_EQ(
detail::decode_url(
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)",
false),
R"(descrip=Gastos áéíóúñÑ 6)");
}
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
string unescapedCharacters = "-_.!~*'()";
@@ -171,6 +186,12 @@ TEST(GetHeaderValueTest, RegularValue) {
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, SetContent) {
Response res;
@@ -383,6 +404,31 @@ TEST(ChunkedEncodingTest, FromHTTPWatch_Online) {
EXPECT_EQ(out, res->body);
}
TEST(HostnameToIPConversionTest, HTTPWatch_Online) {
auto host = "www.httpwatch.com";
auto ip = hosted_at(host);
EXPECT_EQ("191.236.16.12", ip);
std::vector<std::string> addrs;
hosted_at(host, addrs);
EXPECT_EQ(1u, addrs.size());
}
#if 0 // It depends on each test environment...
TEST(HostnameToIPConversionTest, YouTube_Online) {
auto host = "www.youtube.com";
std::vector<std::string> addrs;
hosted_at(host, addrs);
EXPECT_EQ(20u, addrs.size());
auto it = std::find(addrs.begin(), addrs.end(), "2607:f8b0:4006:809::200e");
EXPECT_TRUE(it != addrs.end());
}
#endif
TEST(ChunkedEncodingTest, WithContentReceiver_Online) {
auto host = "www.httpwatch.com";
@@ -564,10 +610,11 @@ TEST(ConnectionErrorTest, InvalidPort) {
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Connection, res.error());
EXPECT_TRUE(Error::Connection == res.error() ||
Error::ConnectionTimeout == res.error());
}
TEST(ConnectionErrorTest, Timeout) {
TEST(ConnectionErrorTest, Timeout_Online) {
auto host = "google.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -579,9 +626,14 @@ TEST(ConnectionErrorTest, Timeout) {
#endif
cli.set_connection_timeout(std::chrono::seconds(2));
// only probe one address type so that the error reason
// correlates to the timed-out IPv4, not the unsupported
// IPv6 connection attempt
cli.set_address_family(AF_INET);
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_TRUE(res.error() == Error::Connection);
EXPECT_EQ(Error::ConnectionTimeout, res.error());
}
TEST(CancelTest, NoCancel_Online) {
@@ -736,6 +788,39 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
}
#endif
TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
auto host = "google.com";
auto another_host = "example.com";
auto wrong_ip = "0.0.0.0";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
Client cli(host);
#endif
cli.set_hostname_addr_map({{another_host, wrong_ip}});
auto res = cli.Get("/");
ASSERT_TRUE(res);
ASSERT_EQ(301, res->status);
}
TEST(SpecifyServerIPAddressTest, RealHostname_Online) {
auto host = "google.com";
auto wrong_ip = "0.0.0.0";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
Client cli(host);
#endif
cli.set_hostname_addr_map({{host, wrong_ip}});
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Connection, res.error());
}
TEST(AbsoluteRedirectTest, Redirect_Online) {
auto host = "nghttp2.org";
@@ -1113,6 +1198,17 @@ TEST(BindServerTest, BindAndListenSeparatelySSL) {
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
SSLServer svr(SERVER_ENCRYPTED_CERT_FILE, SERVER_ENCRYPTED_PRIVATE_KEY_FILE,
nullptr, nullptr, SERVER_ENCRYPTED_PRIVATE_KEY_PASS);
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
svr.stop();
}
#endif
TEST(ErrorHandlerTest, ContentLength) {
Server svr;
@@ -1148,15 +1244,17 @@ TEST(ErrorHandlerTest, ContentLength) {
ASSERT_FALSE(svr.is_running());
}
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
TEST(ExceptionHandlerTest, ContentLength) {
Server svr;
svr.set_exception_handler(
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.set_exception_handler([](const Request & /*req*/, Response &res,
std::exception &e) {
EXPECT_EQ("abc", std::string(e.what()));
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13 at this point
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
@@ -1168,21 +1266,35 @@ TEST(ExceptionHandlerTest, ContentLength) {
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
for (size_t i = 0; i < 10; i++) {
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
cli.set_keep_alive(true);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(NoContentTest, ContentLength) {
Server svr;
@@ -1304,6 +1416,34 @@ TEST(InvalidFormatTest, StatusCode) {
ASSERT_FALSE(svr.is_running());
}
TEST(URLFragmentTest, WithFragment) {
Server svr;
svr.Get("/hi", [](const Request &req, Response & /*res*/) {
EXPECT_TRUE(req.target == "/hi");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi#key1=val1=key2=val2");
EXPECT_TRUE(res);
EXPECT_EQ(200, res->status);
res = cli.Get("/hi%23key1=val1=key2=val2");
EXPECT_TRUE(res);
EXPECT_EQ(404, res->status);
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -1571,6 +1711,11 @@ protected:
EXPECT_EQ("0", req.get_header_value("Content-Length"));
res.set_content("empty-no-content-type", "text/plain");
})
.Post("/post-large",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, LARGE_DATA);
res.set_content(req.body, "text/plain");
})
.Put("/empty-no-content-type",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "");
@@ -1976,6 +2121,13 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
ASSERT_EQ("empty-no-content-type", res->body);
}
TEST_F(ServerTest, PostLarge) {
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
}
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
auto res = cli_.Put("/empty-no-content-type");
ASSERT_TRUE(res);
@@ -3321,7 +3473,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
auto error = Error::Success;
auto client_sock = detail::create_client_socket(
HOST, PORT, AF_UNSPEC, false, nullptr,
HOST, "", PORT, AF_UNSPEC, false, nullptr,
/*connection_timeout_sec=*/5, 0,
/*read_timeout_sec=*/5, 0,
/*write_timeout_sec=*/5, 0, std::string(), error);
@@ -3580,10 +3732,11 @@ TEST(StreamingTest, NoContentLengthStreaming) {
auto get_thread = std::thread([&client]() {
std::string s;
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
s += std::string(data, len);
return true;
});
auto res =
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
s += std::string(data, len);
return true;
});
EXPECT_EQ("aaabbb", s);
});
@@ -3642,6 +3795,7 @@ TEST(MountTest, Unmount) {
ASSERT_FALSE(svr.is_running());
}
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
TEST(ExceptionTest, ThrowExceptionInHandler) {
Server svr;
@@ -3670,6 +3824,7 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(KeepAliveTest, ReadTimeout) {
Server svr;
@@ -3709,6 +3864,36 @@ TEST(KeepAliveTest, ReadTimeout) {
ASSERT_FALSE(svr.is_running());
}
TEST(KeepAliveTest, Issue1041) {
const auto resourcePath = "/hi";
Server svr;
svr.set_keep_alive_timeout(3);
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::milliseconds(200));
Client cli(HOST, PORT);
cli.set_keep_alive(true);
auto result = cli.Get(resourcePath);
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
std::this_thread::sleep_for(std::chrono::seconds(5));
result = cli.Get(resourcePath);
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
svr.stop();
a2.wait();
}
TEST(ClientProblemDetectionTest, ContentProvider) {
Server svr;
@@ -4183,6 +4368,20 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
ASSERT_EQ(200, res->status);
}
#if 0
TEST(SSLClientTest, SetInterfaceWithINET6) {
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
ASSERT_TRUE(cli != nullptr);
cli->set_address_family(AF_INET6);
cli->set_interface("en0");
auto res = cli->Get("/get");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
#endif
TEST(SSLClientServerTest, ClientCertPresent) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
@@ -4392,6 +4591,119 @@ TEST(SSLClientServerTest, SSLConnectTimeout) {
svr.stop();
t.join();
}
TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
auto setup_ssl_ctx_callback = [](SSL_CTX &ssl_ctx) {
SSL_CTX_set_options(&ssl_ctx, SSL_OP_NO_COMPRESSION);
SSL_CTX_set_options(&ssl_ctx,
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_CTX_set_options(&ssl_ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(&ssl_ctx, SSL_OP_NO_SSLv3);
SSL_CTX_set_options(&ssl_ctx, SSL_OP_NO_TLSv1);
SSL_CTX_set_options(&ssl_ctx, SSL_OP_NO_TLSv1_1);
auto ciphers = "ECDHE-RSA-AES128-SHA256:"
"ECDHE-DSS-AES128-SHA256:"
"ECDHE-RSA-AES256-SHA256:"
"ECDHE-DSS-AES256-SHA256:";
SSL_CTX_set_cipher_list(&ssl_ctx, ciphers);
if (SSL_CTX_use_certificate_chain_file(&ssl_ctx, SERVER_CERT_FILE) != 1 ||
SSL_CTX_use_PrivateKey_file(&ssl_ctx, SERVER_PRIVATE_KEY_FILE,
SSL_FILETYPE_PEM) != 1) {
return false;
}
SSL_CTX_load_verify_locations(&ssl_ctx, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
SSL_CTX_set_verify(
&ssl_ctx,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
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);
auto subject_name = X509_get_subject_name(peer_cert);
ASSERT_TRUE(subject_name != nullptr);
std::string common_name;
{
char name[BUFSIZ];
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
name, sizeof(name));
common_name.assign(name, static_cast<size_t>(name_len));
}
EXPECT_EQ("Common Name", common_name);
X509_free(peer_cert);
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(30);
auto res = cli.Get("/test");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
}
// Disabled due to the out-of-memory problem on GitHub Actions Workflows
TEST(SSLClientServerTest, DISABLED_LargeDataTransfer) {
// prepare large data
std::random_device seed_gen;
std::mt19937 random(seed_gen());
constexpr auto large_size_byte = 2147483648UL + 1048576UL; // 2GiB + 1MiB
std::vector<std::uint32_t> binary(large_size_byte / sizeof(std::uint32_t));
std::generate(binary.begin(), binary.end(), [&random]() { return random(); });
// server
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Post("/binary", [&](const Request &req, Response &res) {
EXPECT_EQ(large_size_byte, req.body.size());
EXPECT_EQ(0, std::memcmp(binary.data(), req.body.data(), large_size_byte));
res.set_content(req.body, "application/octet-stream");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// client POST
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
cli.set_read_timeout(std::chrono::seconds(100));
cli.set_write_timeout(std::chrono::seconds(100));
auto res = cli.Post("/binary", reinterpret_cast<char *>(binary.data()),
large_size_byte, "application/octet-stream");
// compare
EXPECT_EQ(200, res->status);
EXPECT_EQ(large_size_byte, res->body.size());
EXPECT_EQ(0, std::memcmp(binary.data(), res->body.data(), large_size_byte));
// cleanup
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
#ifdef _WIN32
@@ -4407,9 +4719,11 @@ TEST(NoSSLSupport, SimpleInterface) {
}
#endif
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
TEST(InvalidScheme, SimpleInterface) {
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
}
#endif
TEST(NoScheme, SimpleInterface) {
Client cli("yahoo.com:80");
@@ -4563,4 +4877,68 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
t.join();
t2.join();
}
TEST(MultipartFormDataTest, LargeData) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Post("/post", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
EXPECT_TRUE(std::string(files[0].name) == "document");
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
EXPECT_TRUE(files[0].filename == "2MB_data");
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
EXPECT_TRUE(files[1].name == "hello");
EXPECT_TRUE(files[1].content == "world");
EXPECT_TRUE(files[1].filename == "");
EXPECT_TRUE(files[1].content_type == "");
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
}
});
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '.');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
auto res = cli.Post("/post", items);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
svr.stop();
t.join();
}
#endif