Compare commits

..

66 Commits

Author SHA1 Message Date
yhirose eb30f15363 Release v0.18.6 2025-02-05 19:14:20 -05:00
yhirose 4941d5b56b Fix #2033 (#2039) 2025-02-05 12:46:33 -05:00
Florian Albrechtskirchinger 9bbb4741b4 Run clang-format (#2037) 2025-02-02 22:32:33 -05:00
yhirose 282f2feb77 Add a unit test 2025-02-01 22:11:15 -05:00
alex-cornford 60a1f00618 Support building httplib.h on OpenVMS x86 systems (#2031)
Modify for OpenVMS x86 C++. Make tests on OpenVMS currently not supported due to no cmake support.
Changes tested on OpenVMS clang C++ and Fedora & GCC
2025-01-28 18:44:22 -05:00
yhirose 9104054ca5 Fix README example 2025-01-27 13:37:16 -05:00
Baiyies d69f144a99 Update httplib.h (#2030)
fix 'max'
2025-01-26 08:50:10 -05:00
yhirose 929dfbd348 Update copyright year 2025-01-20 00:32:10 -05:00
yhirose 3047183fd9 Update README 2025-01-20 00:02:02 -05:00
yhirose ef5e4044f1 Update README 2025-01-19 23:46:12 -05:00
yhirose 3779800322 Release v0.18.5 2025-01-17 17:38:03 -05:00
yhirose 986a20fb7d Resolve #2017 (#2022)
* Resolve #2017

* Fix warning

* Update README
2025-01-17 17:37:07 -05:00
yhirose 8311e1105f Fix Windows build problem 2025-01-16 23:26:04 -05:00
yhirose ba6845925d Fix #2014 2025-01-16 23:10:58 -05:00
yhirose 343a0fc073 Fix #2011 2025-01-16 21:38:45 -05:00
yhirose 54f8a4d0f3 Release v0.18.4 2025-01-16 01:00:25 -05:00
yhirose 9c36aae4b7 Fix HTTP Response Splitting Vulnerability 2025-01-16 00:04:33 -05:00
yhirose b766025a83 clangformat 2025-01-16 00:03:10 -05:00
yhirose 9b5f76f833 Fix #2012 2024-12-27 17:19:23 -05:00
sinnren d647f484a4 fix:set_file_content with range request return 416. (#2010)
Co-authored-by: fenlog <bakurise@qq.com>
2024-12-24 09:38:59 -05:00
Sergey Bobrenok 8794792baa Treat out-of-range last_pos as the end of the content (#2009)
RFC-9110 '14.1.2. Byte Ranges':
A client can limit the number of bytes requested without knowing the
size of the selected representation. If the last-pos value is absent,
or if the value is greater than or equal to the current length of the
representation data, the byte range is interpreted as the remainder of
the representation (i.e., the server replaces the value of last-pos
with a value that is one less than the current length of the selected
representation).

https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
2024-12-23 13:14:36 -05:00
yhirose b85768c1f3 Fix #2005 2024-12-16 17:43:50 -05:00
yhirose e6d71bd702 Add a unit test for Issue #2004 2024-12-12 18:15:22 -05:00
yhirose 258992a160 Changed to use non-blocking socket in is_ssl_peer_could_be_closed 2024-12-03 19:26:08 -05:00
yhirose a7bc00e330 Release v0.18.3 2024-12-03 06:33:00 -05:00
yhirose 11a40584e9 Fix #1998 2024-12-03 00:38:20 -05:00
yhirose 3e86bdb4d8 Fix #1997 (#2001) 2024-12-03 00:11:29 -05:00
Pavel P c817d65695 Fix casting uint64_t to size_t for 32-bit builds (#1999) 2024-12-02 11:09:52 -05:00
yhirose 51dee793fe Release v0.18.2 2024-11-29 20:49:50 -05:00
yhirose 457fc4306e Fix #1993 2024-11-29 20:46:48 -05:00
yhirose 4f5b003e76 Fix #1992 2024-11-28 20:40:38 -05:00
yhirose 5421e27106 Fix a compiler warning 2024-11-28 20:39:26 -05:00
yhirose fe07660f40 Fix #1986 (#1988) 2024-11-27 12:18:35 -05:00
yhirose da2f9e476e Fix #1985 (#1989) 2024-11-27 12:18:23 -05:00
sebastianas 1a7a7ed1c3 test: Don't check for the exact size of compressed content. (#1984)
The testsuite checks for the exact size of the compressed content. The
exact size can change if the zlib library is using a different strategy.
In thise case using zlib-ng results in a slightly larger content leading
to a failure in the test.

Check that the compressed content is less than 10MiB which is a tenth of
the orignal content and proves that compression works.

Signed-off-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
2024-11-25 15:46:41 -05:00
yhirose 413994912d Update vcxproj files 2024-11-16 11:14:13 -05:00
yhirose 01dcf1d0ad Fix #1969 (without unnecessary sleep_for) (#1982) 2024-11-16 10:56:57 -05:00
yhirose 8e378779c2 Update README 2024-11-16 09:45:04 -05:00
yhirose 970b52897c Fix #1980
Fix #1980
2024-11-16 02:09:52 -05:00
yhirose 412ba04d19 Fix problem caused by #1975 2024-11-14 20:33:08 -05:00
yhirose bfef4b3e9b Fix #1975 2024-11-14 17:27:28 -05:00
yhirose 7bd316f3d0 Fix #1977 2024-11-14 16:46:27 -05:00
yhirose 26208363ee Fix warning 2024-11-14 16:46:09 -05:00
yhirose b1b4bb8850 clangformat 2024-11-13 22:50:03 -05:00
yhirose 9dd565b6e3 Resolve #1973 (#1976)
* Fix #1973

* Fixed problems with 'Language for non-Unicode programs' setting on Windows

* Fix problems on English locale
2024-11-13 22:47:09 -05:00
yhirose 924f214303 Added unit test for exception handler 2024-11-02 07:23:44 -04:00
yhirose 5c1a34e766 Release v0.18.1 2024-10-18 17:16:54 -04:00
yhirose fa90d06dd5 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-18 17:16:14 -04:00
Jiwoo Park d869054318 Allow empty header values (#1965) 2024-10-18 10:16:48 -04:00
Peter Boström 0cc1ca9a8d Remove extra semicolon (#1963)
This fixes a -Wc++98-compat-extra-semi instance.
2024-10-15 00:09:04 -04:00
yhirose 3701195033 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-14 21:32:28 -04:00
Peter Boström f884a56258 Remove space between operator"" and _t (#1962)
This should fix a -Wdeprecated-literal-operator instance since this is
deprecated as a result of CWG2521 (iiuc C++23).
2024-10-14 21:32:13 -04:00
yhirose d79633ff52 clangformat 2024-10-11 14:49:46 -04:00
yhirose e0ebc431dc Fix #1959 2024-10-11 13:43:01 -04:00
Andrew McDaniel 131bc6c674 Add documentation for using Unix domain sockets. (#1954)
* Add documentation for using unix domain sockets.

* Formatting
2024-10-03 10:53:25 -04:00
yhirose 10d68cff50 Added a unit test for #1946 2024-09-26 22:24:44 -04:00
zjyhjqs 996acc5253 Feat: add CPack support (#1950) 2024-09-25 00:19:41 -04:00
yhirose 7c4799d0cf Fix #1798 for CMake (#1944) 2024-09-19 18:33:32 -04:00
yhirose c239087332 Fix Mafile errors 2024-09-17 18:37:44 -04:00
Andrea Pappacoda 7018e9263d test(meson): copy files in www directory (#1941)
These files were added in commits
2d01e71286 and
b8315278cb
2024-09-17 18:06:01 -04:00
yhirose 4990b4b4b7 Fix problems with SSLSlientServerTest.* tests 2024-09-17 17:00:17 -04:00
Andrea Pappacoda 5064373c23 test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0 (#1940)
* build(meson): bump minimum version to 0.62.0

This allows making some minor cleanups

* test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0

Since OpenSSL commit
<https://github.com/openssl/openssl/commit/342e3652c791bdb06e08abcc169b4456c83ccd00>,
the default X.509 certificate format generated with the `openssl req`
command has been changed to X.509 v3 from X.509 v1.

For some reason, this change breaks cpp-httplib's SSLClientServerTest.*
tests.

To fix the test failures, this patch passes the '-x509v1' flag instead
of '-x509' when OpenSSL 3.2.0 or newer is detected. To detect the
version of a command line utility, Meson 0.62.0 or later is required.

Fixes <https://github.com/yhirose/cpp-httplib/issues/1798>, but only for
the Meson build system.
2024-09-17 16:58:09 -04:00
yhirose 6c93aea59a Revert "Enabled HostnameToIPConversionTest.YouTube_Online partially"
This reverts commit 6553cdedab.
2024-09-15 01:18:20 -04:00
yhirose 6553cdedab Enabled HostnameToIPConversionTest.YouTube_Online partially 2024-09-13 20:45:59 -04:00
yhirose a61b2427b0 Update benchmark base vertion to 0.18.0 2024-09-13 20:34:30 -04:00
yhirose af4ece3d5f Update benchmark/Makefile 2024-09-12 20:41:56 -04:00
20 changed files with 1305 additions and 446 deletions
+32 -10
View File
@@ -11,7 +11,7 @@ jobs:
- name: install libraries
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
- name: build and run tests
run: cd test && make -j4
run: cd test && make
- name: run fuzz test target
run: cd test && make fuzz_test
@@ -21,23 +21,45 @@ jobs:
- name: checkout
uses: actions/checkout@v4
- name: build and run tests
run: |
cd test && make -j2
run: cd test && make
- name: run fuzz test target
run: cd test && make fuzz_test
windows:
runs-on: windows-latest
steps:
- name: prepare git for checkout on windows
- name: Prepare Git for Checkout on Windows
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: checkout
- name: Checkout
uses: actions/checkout@v4
- name: setup msbuild on windows
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Setup msbuild on windows
uses: microsoft/setup-msbuild@v2
- name: make-windows
- name: Install libraries
run: |
cd test
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
x64\Release\test.exe
vcpkg install gtest curl zlib brotli
choco install openssl
- name: Configure CMake with SSL
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
- name: Build with with SSL
run: cmake --build build --config Release
- name: Run tests with SSL
run: ctest --output-on-failure --test-dir build -C Release
- name: Configure CMake without SSL
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
- name: Build without SSL
run: cmake --build build-no-ssl --config Release
- name: Run tests without SSL
run: ctest --output-on-failure --test-dir build-no-ssl -C Release
env:
VCPKG_ROOT: "C:/vcpkg"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
+2
View File
@@ -299,6 +299,8 @@ if(HTTPLIB_INSTALL)
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
include(CPack)
endif()
if(HTTPLIB_TEST)
+56 -11
View File
@@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080);
#include "path/to/httplib.h"
// HTTP
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
httplib::Client cli("http://yhirose.github.io");
// HTTPS
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
httplib::Client cli("https://yhirose.github.io");
auto res = cli.Get("/hi");
res->status;
@@ -79,7 +79,7 @@ cli.set_ca_cert_path("./ca-bundle.crt");
cli.enable_server_certificate_verification(false);
// Disable host verification
cli.enable_server_host_verification(false);
cli.enable_server_hostname_verification(false);
```
> [!NOTE]
@@ -125,6 +125,21 @@ int main(void)
res.set_content(req.body, "text/plain");
});
// If the handler takes time to finish, you can also poll the connection state
svr.Get("/task", [&](const Request& req, Response& res) {
const char * result = nullptr;
process.run(); // for example, starting an external process
while (result == nullptr) {
sleep(1);
if (req.is_connection_closed()) {
process.kill(); // kill the process
return;
}
result = process.stdout(); // != nullptr if the process finishes
}
res.set_content(result, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
@@ -557,18 +572,18 @@ enum Error {
```c++
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
{ "Hello", "World!" }
};
auto res = cli.Get("/hi", headers);
```
or
```c++
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
auto res = cli.Get("/hi", {{"Hello", "World!"}});
```
or
```c++
cli.set_default_headers({
{ "Accept-Encoding", "gzip, deflate" }
{ "Hello", "World!" }
});
auto res = cli.Get("/hi");
```
@@ -823,6 +838,21 @@ The server can apply compression to the following MIME type contents:
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
Please see https://github.com/google/brotli for more detail.
### Default `Accept-Encoding` value
The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same.
```c++
res = cli.Get("/resource/foo");
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
```
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
```c++
res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}});
```
### Compress request body on client
```c++
@@ -834,8 +864,9 @@ res = cli.Post("/resource/foo", "...", "text/plain");
```c++
cli.set_decompress(false);
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res = cli.Get("/resource/foo");
res->body; // Compressed data
```
Use `poll` instead of `select`
@@ -843,6 +874,23 @@ 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`.
Unix Domain Socket Support
--------------------------
Unix Domain Socket support is available on Linux and macOS.
```c++
// Server
httplib::Server svr("./my-socket.sock");
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
// Client
httplib::Client cli("./my-socket.sock");
cli.set_address_family(AF_UNIX);
```
"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
Split httplib.h into .h and .cc
-------------------------------
@@ -883,9 +931,6 @@ From Docker Hub
```bash
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
@@ -923,7 +968,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
License
-------
MIT license (© 2024 Yuji Hirose)
MIT license (© 2025 Yuji Hirose)
Special Thanks To
-----------------
+7 -5
View File
@@ -1,6 +1,6 @@
CXXFLAGS = -std=c++11 -O2 -I..
THEAD_POOL_COUNT = 16
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
BENCH = bombardier -c 10 -d 5s localhost:8080
MONITOR = ali http://localhost:8080
@@ -18,11 +18,11 @@ run : server
@./server
server : cpp-httplib/main.cpp ../httplib.h
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
# cpp-httplib
bench-base: server-base
@echo "---------------------\n cpp-httplib v0.17.0\n---------------------\n"
@echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n"
@./server-base & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
@@ -33,7 +33,7 @@ run-base : server-base
@./server-base
server-base : cpp-httplib-base/main.cpp cpp-httplib-base/httplib.h
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib-base/main.cpp
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-base/main.cpp
# crow
bench-crow: server-crow
@@ -51,10 +51,12 @@ server-crow : crow/main.cpp
g++ -o $@ $(CXXFLAGS) crow/main.cpp
# misc
build: server server-base server-crow
bench-all: bench-crow bench bench-base
issue:
$(BENCH)
bombardier -c 10 -d 30s localhost:8080
clean:
rm -rf server*
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
//
// main.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
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 issue
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client
server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
+5 -5
View File
@@ -22,34 +22,34 @@
<ProjectGuid>{6DB1FC63-B153-4279-92B7-D8A11AF285D6}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>client</RootNamespace>
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
+8 -8
View File
@@ -18,38 +18,41 @@
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="simplesvr.cc" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{864CD288-050A-4C8B-9BEF-3048BD876C5B}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>sample</RootNamespace>
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
@@ -151,9 +154,6 @@
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="server.cc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
+1 -1
View File
@@ -1,7 +1,7 @@
//
// server_and_client.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
+272 -74
View File
@@ -1,14 +1,14 @@
//
// httplib.h
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.18.0"
#define CPPHTTPLIB_VERSION "0.18.6"
/*
* Configuration
@@ -18,6 +18,10 @@
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
@@ -214,7 +218,9 @@ using socket_t = SOCKET;
#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
#ifndef __VMS
#include <sys/select.h>
#endif
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
@@ -365,8 +371,9 @@ inline unsigned char to_lower(int c) {
inline bool equal(const std::string &a, const std::string &b) {
return a.size() == b.size() &&
std::equal(a.begin(), a.end(), b.begin(),
[](char ca, char cb) { return to_lower(ca) == to_lower(cb); });
std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
return to_lower(ca) == to_lower(cb);
});
}
struct equal_to {
@@ -391,7 +398,7 @@ struct hash {
}
};
}; // namespace case_ignore
} // namespace case_ignore
// This is based on
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
@@ -607,6 +614,7 @@ using Ranges = std::vector<Range>;
struct Request {
std::string method;
std::string path;
Params params;
Headers headers;
std::string body;
@@ -618,11 +626,11 @@ struct Request {
// for server
std::string version;
std::string target;
Params params;
MultipartFormDataMap files;
Ranges ranges;
Match matches;
std::unordered_map<std::string, std::string> path_params;
std::function<bool()> is_connection_closed = []() { return true; };
// for client
ResponseHandler response_handler;
@@ -1577,6 +1585,9 @@ private:
bool send_(Request &req, Response &res, Error &error);
Result send_(Request &&req);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
bool is_ssl_peer_could_be_closed(SSL *ssl) const;
#endif
socket_t create_client_socket(Error &error) const;
bool read_response_line(Stream &strm, const Request &req,
Response &res) const;
@@ -2004,18 +2015,34 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}
inline bool is_numeric(const std::string &str) {
return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
}
inline uint64_t get_header_value_u64(const Headers &headers,
const std::string &key, uint64_t def,
size_t id) {
size_t id, bool &is_invalid_value) {
is_invalid_value = false;
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
if (it != rng.second) {
return std::strtoull(it->second.data(), nullptr, 10);
if (is_numeric(it->second)) {
return std::strtoull(it->second.data(), nullptr, 10);
} else {
is_invalid_value = true;
}
}
return def;
}
inline uint64_t get_header_value_u64(const Headers &headers,
const std::string &key, uint64_t def,
size_t id) {
bool dummy = false;
return get_header_value_u64(headers, key, def, id, dummy);
}
} // namespace detail
inline uint64_t Request::get_header_value_u64(const std::string &key,
@@ -2033,8 +2060,6 @@ inline void default_socket_options(socket_t sock) {
#ifdef _WIN32
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *>(&opt), sizeof(opt));
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
#ifdef SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
@@ -2253,13 +2278,33 @@ make_basic_authentication_header(const std::string &username,
namespace detail {
#if defined(_WIN32)
inline std::wstring u8string_to_wstring(const char *s) {
std::wstring ws;
auto len = static_cast<int>(strlen(s));
auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
if (wlen > 0) {
ws.resize(wlen);
wlen = ::MultiByteToWideChar(
CP_UTF8, 0, s, len,
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
}
return ws;
}
#endif
struct FileStat {
FileStat(const std::string &path);
bool is_file() const;
bool is_dir() const;
private:
#if defined(_WIN32)
struct _stat st_;
#else
struct stat st_;
#endif
int ret_ = -1;
};
@@ -2478,6 +2523,60 @@ private:
bool is_open_empty_file = false;
};
// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
namespace fields {
inline bool is_token_char(char c) {
return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
}
inline bool is_token(const std::string &s) {
if (s.empty()) { return false; }
for (auto c : s) {
if (!is_token_char(c)) { return false; }
}
return true;
}
inline bool is_field_name(const std::string &s) { return is_token(s); }
inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
inline bool is_field_content(const std::string &s) {
if (s.empty()) { return true; }
if (s.size() == 1) {
return is_field_vchar(s[0]);
} else if (s.size() == 2) {
return is_field_vchar(s[0]) && is_field_vchar(s[1]);
} else {
size_t i = 0;
if (!is_field_vchar(s[i])) { return false; }
i++;
while (i < s.size() - 1) {
auto c = s[i++];
if (c == ' ' || c == '\t' || is_field_vchar(c)) {
} else {
return false;
}
}
return is_field_vchar(s[i]);
}
}
inline bool is_field_value(const std::string &s) { return is_field_content(s); }
} // namespace fields
} // namespace detail
// ----------------------------------------------------------------------------
@@ -2634,7 +2733,12 @@ inline bool is_valid_path(const std::string &path) {
}
inline FileStat::FileStat(const std::string &path) {
#if defined(_WIN32)
auto wpath = u8string_to_wstring(path.c_str());
ret_ = _wstat(wpath.c_str(), &st_);
#else
ret_ = stat(path.c_str(), &st_);
#endif
}
inline bool FileStat::is_file() const {
return ret_ >= 0 && S_ISREG(st_.st_mode);
@@ -2896,9 +3000,7 @@ inline void stream_line_reader::append(char c) {
}
}
inline mmap::mmap(const char *path) {
open(path);
}
inline mmap::mmap(const char *path) { open(path); }
inline mmap::~mmap() { close(); }
@@ -2906,10 +3008,8 @@ inline bool mmap::open(const char *path) {
close();
#if defined(_WIN32)
std::wstring wpath;
for (size_t i = 0; i < strlen(path); i++) {
wpath += path[i];
}
auto wpath = u8string_to_wstring(path);
if (wpath.empty()) { return false; }
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
@@ -3251,6 +3351,39 @@ private:
};
#endif
inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
time_t keep_alive_timeout_sec) {
using namespace std::chrono;
const auto interval_usec =
CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
// Avoid expensive `steady_clock::now()` call for the first time
if (select_read(sock, 0, interval_usec) > 0) { return true; }
const auto start = steady_clock::now() - microseconds{interval_usec};
const auto timeout = seconds{keep_alive_timeout_sec};
while (true) {
if (svr_sock == INVALID_SOCKET) {
break; // Server socket is closed
}
auto val = select_read(sock, 0, interval_usec);
if (val < 0) {
break; // Ssocket error
} else if (val == 0) {
if (steady_clock::now() - start > timeout) {
break; // Timeout
}
} else {
return true; // Ready for read
}
}
return false;
}
template <typename T>
inline bool
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
@@ -3259,8 +3392,7 @@ process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
assert(keep_alive_max_count > 0);
auto ret = false;
auto count = keep_alive_max_count;
while (svr_sock != INVALID_SOCKET && count > 0 &&
select_read(sock, keep_alive_timeout_sec, 0) > 0) {
while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
auto close_connection = count == 1;
auto connection_closed = false;
ret = callback(close_connection, connection_closed);
@@ -3722,7 +3854,7 @@ inline unsigned int str2tag(const std::string &s) {
namespace udl {
inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
inline constexpr unsigned int operator""_t(const char *s, size_t l) {
return str2tag_core(s, l, 0);
}
@@ -4077,27 +4209,26 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
p++;
}
if (p < end) {
if (p <= end) {
auto key_len = key_end - beg;
if (!key_len) { return false; }
auto key = std::string(beg, key_end);
auto val = case_ignore::equal(key, "Location")
? std::string(p, end)
: decode_url(std::string(p, end), false);
// auto val = (case_ignore::equal(key, "Location") ||
// case_ignore::equal(key, "Referer"))
// ? std::string(p, end)
// : decode_url(std::string(p, end), false);
auto val = std::string(p, end);
// NOTE: From RFC 9110:
// Field values containing CR, LF, or NUL characters are
// invalid and dangerous, due to the varying ways that
// implementations might parse and interpret those
// characters; a recipient of CR, LF, or NUL within a field
// value MUST either reject the message or replace each of
// those characters with SP before further processing or
// forwarding of that message.
static const std::string CR_LF_NUL("\r\n\0", 3);
if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; }
if (!detail::fields::is_field_value(val)) { return false; }
if (case_ignore::equal(key, "Location") ||
case_ignore::equal(key, "Referer")) {
fn(key, val);
} else {
fn(key, decode_url(val, false));
}
fn(key, val);
return true;
}
@@ -4133,7 +4264,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
if (!parse_header(line_reader.ptr(), end,
[&](const std::string &key, std::string &val) {
[&](const std::string &key, const std::string &val) {
headers.emplace(key, val);
})) {
return false;
@@ -4325,8 +4456,14 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
} else if (!has_header(x.headers, "Content-Length")) {
ret = read_content_without_length(strm, out);
} else {
auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0);
if (len > payload_max_length) {
auto is_invalid_value = false;
auto len = get_header_value_u64(
x.headers, "Content-Length",
(std::numeric_limits<uint64_t>::max)(), 0, is_invalid_value);
if (is_invalid_value) {
ret = false;
} else if (len > payload_max_length) {
exceed_payload_max_length = true;
skip_content_with_length(strm, len);
ret = false;
@@ -5095,7 +5232,18 @@ inline bool range_error(Request &req, Response &res) {
last_pos = contant_len - 1;
}
if (last_pos == -1) { last_pos = contant_len - 1; }
// NOTE: RFC-9110 '14.1.2. Byte Ranges':
// A client can limit the number of bytes requested without knowing the
// size of the selected representation. If the last-pos value is absent,
// or if the value is greater than or equal to the current length of the
// representation data, the byte range is interpreted as the remainder of
// the representation (i.e., the server replaces the value of last-pos
// with a value that is one less than the current length of the selected
// representation).
// https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
if (last_pos == -1 || last_pos >= contant_len) {
last_pos = contant_len - 1;
}
// Range must be within content length
if (!(0 <= first_pos && first_pos <= last_pos &&
@@ -5627,7 +5775,8 @@ inline size_t Request::get_header_value_count(const std::string &key) const {
inline void Request::set_header(const std::string &key,
const std::string &val) {
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
if (detail::fields::is_field_name(key) &&
detail::fields::is_field_value(val)) {
headers.emplace(key, val);
}
}
@@ -5693,13 +5842,14 @@ inline size_t Response::get_header_value_count(const std::string &key) const {
inline void Response::set_header(const std::string &key,
const std::string &val) {
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
if (detail::fields::is_field_name(key) &&
detail::fields::is_field_value(val)) {
headers.emplace(key, val);
}
}
inline void Response::set_redirect(const std::string &url, int stat) {
if (!detail::has_crlf(url)) {
if (detail::fields::is_field_value(url)) {
set_header("Location", url);
if (300 <= stat && stat < 400) {
this->status = stat;
@@ -7069,6 +7219,11 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
}
}
// Setup `is_connection_closed` method
req.is_connection_closed = [&]() {
return !detail::is_socket_alive(strm.socket());
};
// Routing
auto routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
@@ -7111,14 +7266,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
: StatusCode::PartialContent_206;
}
if (detail::range_error(req, res)) {
res.body.clear();
res.content_length_ = 0;
res.content_provider_ = nullptr;
res.status = StatusCode::RangeNotSatisfiable_416;
return write_response(strm, close_connection, req, res);
}
// Serve file content by using a content provider
if (!res.file_content_path_.empty()) {
const auto &path = res.file_content_path_;
@@ -7145,6 +7292,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
});
}
if (detail::range_error(req, res)) {
res.body.clear();
res.content_length_ = 0;
res.content_provider_ = nullptr;
res.status = StatusCode::RangeNotSatisfiable_416;
return write_response(strm, close_connection, req, res);
}
return write_response_with_content(strm, close_connection, req, res);
} else {
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
@@ -7355,6 +7510,18 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
return ret;
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const {
detail::set_nonblocking(socket_.sock, true);
auto se = detail::scope_exit(
[&]() { detail::set_nonblocking(socket_.sock, false); });
char buf[1];
return !SSL_peek(ssl, buf, 1) &&
SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
}
#endif
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
{
std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -7366,6 +7533,13 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
auto is_alive = false;
if (socket_.is_open()) {
is_alive = detail::is_socket_alive(socket_.sock);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if (is_alive && is_ssl()) {
if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
}
#endif
if (!is_alive) {
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
// like the other side has already closed the connection Also, there
@@ -7642,12 +7816,26 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
if (!req.has_header("User-Agent")) {
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
req.set_header("User-Agent", agent);
}
if (!req.content_receiver) {
if (!req.has_header("Accept-Encoding")) {
std::string accept_encoding;
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
accept_encoding = "br";
#endif
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (!accept_encoding.empty()) { accept_encoding += ", "; }
accept_encoding += "gzip, deflate";
#endif
req.set_header("Accept-Encoding", accept_encoding);
}
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
if (!req.has_header("User-Agent")) {
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
req.set_header("User-Agent", agent);
}
#endif
};
if (req.body.empty()) {
if (req.content_provider_) {
@@ -7707,7 +7895,13 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
{
detail::BufferStream bstrm;
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
const auto &path_with_query =
req.params.empty() ? req.path
: append_query_params(req.path, req.params);
const auto &path =
url_encode_ ? detail::encode_url(path_with_query) : path_with_query;
detail::write_request_line(bstrm, req.method, path);
header_writer_(bstrm, req.headers);
@@ -7848,9 +8042,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
if (is_ssl()) {
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
if (!is_proxy_enabled) {
char buf[1];
if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
if (is_ssl_peer_could_be_closed(socket_.ssl)) {
error = Error::SSLPeerCouldBeClosed_;
return false;
}
@@ -7868,7 +8060,9 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
// Body
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
req.method != "CONNECT") {
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
auto redirect = 300 < res.status && res.status < 400 &&
res.status != StatusCode::NotModified_304 &&
follow_location_;
if (req.response_handler && !redirect) {
if (!req.response_handler(res)) {
@@ -7889,9 +8083,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
: static_cast<ContentReceiverWithProgress>(
[&](const char *buf, size_t n, uint64_t /*off*/,
uint64_t /*len*/) {
if (res.body.size() + n > res.body.max_size()) {
return false;
}
assert(res.body.size() + n <= res.body.max_size());
res.body.append(buf, n);
return true;
});
@@ -7905,18 +8097,23 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
if (res.has_header("Content-Length")) {
if (!req.content_receiver) {
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
res.body.max_size());
if (len > 0) { res.body.reserve(len); }
auto len = res.get_header_value_u64("Content-Length");
if (len > res.body.max_size()) {
error = Error::Read;
return false;
}
res.body.reserve(static_cast<size_t>(len));
}
}
int dummy_status;
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
dummy_status, std::move(progress), std::move(out),
decompress_)) {
if (error != Error::Canceled) { error = Error::Read; }
return false;
if (res.status != StatusCode::NotModified_304) {
int dummy_status;
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
dummy_status, std::move(progress),
std::move(out), decompress_)) {
if (error != Error::Canceled) { error = Error::Read; }
return false;
}
}
}
@@ -8805,6 +9002,7 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
// best-efforts.
if (shutdown_gracefully) {
#ifdef _WIN32
(void)(sock);
SSL_shutdown(ssl);
#else
timeval tv;
@@ -9454,8 +9652,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
auto type = GEN_DNS;
struct in6_addr addr6 {};
struct in_addr addr {};
struct in6_addr addr6{};
struct in_addr addr{};
size_t addr_len = 0;
#ifndef __MINGW32__
+4 -6
View File
@@ -13,7 +13,7 @@ project(
'b_lto=true',
'warning_level=3'
],
meson_version: '>=0.47.0'
meson_version: '>=0.62.0'
)
# Check just in case downstream decides to edit the source
@@ -98,20 +98,18 @@ if get_option('cpp-httplib_compile')
)
else
install_headers('httplib.h')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, 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'),
install_dir: get_option('datadir')/'pkgconfig',
url: 'https://github.com/yhirose/cpp-httplib',
version: version
)
endif
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
endif
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
if get_option('cpp-httplib_test')
subdir('test')
+7 -2
View File
@@ -37,6 +37,11 @@ file(
)
if(HTTPLIB_IS_USING_OPENSSL)
if (OPENSSL_VERSION VERSION_LESS "3.2.0")
set(OPENSSL_X509_FLAG "-x509")
else()
set(OPENSSL_X509_FLAG "-x509v1")
endif()
find_program(OPENSSL_COMMAND
NAMES openssl
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
@@ -56,7 +61,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
@@ -67,7 +72,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
OUTPUT_FILE rootCA.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
+2 -12
View File
@@ -63,18 +63,8 @@ httplib.cc : ../httplib.h
python3 ../split.py -o .
cert.pem:
openssl genrsa 2048 > key.pem
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
openssl genrsa 2048 > rootCA.key.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
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 .
./gen-certs.sh
clean:
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
if [[ $(openssl version) =~ 3\.[2-9]\.[0-9]+ ]]; then
OPENSSL_X509_FLAG='-x509v1'
else
OPENSSL_X509_FLAG='-x509'
fi
openssl genrsa 2048 > key.pem
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
openssl genrsa 2048 > rootCA.key.pem
openssl req $OPENSSL_X509_FLAG -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
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
+7 -6
View File
@@ -6,6 +6,7 @@ gtest_dep = dependency('gtest', main: true)
libcurl_dep = dependency('libcurl')
openssl = find_program('openssl')
test_conf = files('test.conf')
req_x509_flag = openssl.version().version_compare('>=3.2.0') ? '-x509v1' : '-x509'
key_pem = custom_target(
'key_pem',
@@ -31,7 +32,7 @@ cert2_pem = custom_target(
'cert2_pem',
input: key_pem,
output: 'cert2.pem',
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
key_encrypted_pem = custom_target(
@@ -44,7 +45,7 @@ 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']
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
rootca_key_pem = custom_target(
@@ -57,7 +58,7 @@ rootca_cert_pem = custom_target(
'rootca_cert_pem',
input: rootca_key_pem,
output: 'rootCA.cert.pem',
command: [openssl, 'req', '-x509', '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
command: [openssl, 'req', req_x509_flag, '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
)
client_key_pem = custom_target(
@@ -103,9 +104,9 @@ client_encrypted_cert_pem = custom_target(
# 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)
subdir(join_paths('www', 'dir'))
subdir(join_paths('www2', 'dir'))
subdir(join_paths('www3', 'dir'))
subdir('www')
subdir('www2'/'dir')
subdir('www3'/'dir')
# GoogleTest 1.13.0 requires C++14
test_options = []
+432 -61
View File
@@ -1,3 +1,4 @@
// NOTE: This file should be saved as UTF-8 w/ BOM
#include <httplib.h>
#include <signal.h>
@@ -241,7 +242,7 @@ TEST(DecodeURLTest, PercentCharacter) {
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)");
u8"descrip=Gastos áéíóúñÑ 6");
}
TEST(DecodeURLTest, PercentCharacterNUL) {
@@ -267,9 +268,9 @@ TEST(EncodeQueryParamTest, ParseReservedCharactersTest) {
}
TEST(EncodeQueryParamTest, TestUTF8Characters) {
string chineseCharacters = "中国語";
string russianCharacters = "дом";
string brazilianCharacters = "óculos";
string chineseCharacters = u8"中国語";
string russianCharacters = u8"дом";
string brazilianCharacters = u8"óculos";
EXPECT_EQ(detail::encode_query_param(chineseCharacters),
"%E4%B8%AD%E5%9B%BD%E8%AA%9E");
@@ -510,6 +511,15 @@ TEST(GetHeaderValueTest, RegularValueInt) {
EXPECT_EQ(100ull, val);
}
TEST(GetHeaderValueTest, RegularInvalidValueInt) {
Headers headers = {{"Content-Length", "x"}};
auto is_invalid_value = false;
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0,
is_invalid_value);
EXPECT_EQ(0ull, val);
EXPECT_TRUE(is_invalid_value);
}
TEST(GetHeaderValueTest, Range) {
{
Headers headers = {make_range_header({{1, -1}})};
@@ -1853,6 +1863,32 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
}
}
TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
Server svr;
svr.Get("/", [](const Request &req, Response &) {
EXPECT_EQ("\x0A", req.get_param_value("something"));
});
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_url_encode(false);
auto res = cli.Get("/?something=%0A");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
TEST(BindServerTest, DISABLED_BindDualStack) {
Server svr;
@@ -2003,7 +2039,7 @@ TEST(ErrorHandlerTest, ContentLength) {
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@@ -2013,7 +2049,46 @@ TEST(ErrorHandlerTest, ContentLength) {
}
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
TEST(ExceptionHandlerTest, ContentLength) {
TEST(ExceptionTest, WithoutExceptionHandler) {
Server svr;
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception...");
});
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception\r\n...");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli("localhost", PORT);
{
auto res = cli.Get("/exception");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
}
{
auto res = cli.Get("/unknown");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
}
}
TEST(ExceptionTest, WithExceptionHandler) {
Server svr;
svr.set_exception_handler([](const Request & /*req*/, Response &res,
@@ -2021,7 +2096,9 @@ TEST(ExceptionHandlerTest, ContentLength) {
EXPECT_FALSE(ep == nullptr);
try {
std::rethrow_exception(ep);
} catch (std::exception &e) { EXPECT_EQ("abc", std::string(e.what())); }
} catch (std::exception &e) {
EXPECT_EQ("abc", std::string(e.what()));
} catch (...) {}
res.status = StatusCode::InternalServerError_500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13 at this point
@@ -2045,7 +2122,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
Client cli(HOST, PORT);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@@ -2056,7 +2133,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
cli.set_keep_alive(true);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@@ -2065,6 +2142,66 @@ TEST(ExceptionHandlerTest, ContentLength) {
}
}
}
TEST(ExceptionTest, AndErrorHandler) {
Server svr;
svr.set_error_handler([](const Request & /*req*/, Response &res) {
if (res.body.empty()) { res.set_content("NOT_FOUND", "text/html"); }
});
svr.set_exception_handler(
[](const Request & /*req*/, Response &res, std::exception_ptr ep) {
EXPECT_FALSE(ep == nullptr);
try {
std::rethrow_exception(ep);
} catch (std::exception &e) {
res.set_content(e.what(), "text/html");
} catch (...) {}
res.status = StatusCode::InternalServerError_500;
});
svr.Get("/exception", [](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("EXCEPTION");
});
svr.Get("/error", [](const Request & /*req*/, Response &res) {
res.set_content("ERROR", "text/html");
res.status = StatusCode::InternalServerError_500;
});
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);
{
auto res = cli.Get("/exception");
ASSERT_TRUE(res);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("EXCEPTION", res->body);
}
{
auto res = cli.Get("/error");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
EXPECT_EQ("ERROR", res->body);
}
{
auto res = cli.Get("/invalid");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
EXPECT_EQ("NOT_FOUND", res->body);
}
}
#endif
TEST(NoContentTest, ContentLength) {
@@ -2934,6 +3071,16 @@ TEST_F(ServerTest, GetFileContent) {
EXPECT_EQ("test.html", res->body);
}
TEST_F(ServerTest, GetFileContentWithRange) {
auto res = cli_.Get("/file_content", {{make_range_header({{1, 3}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("bytes 1-3/9", res->get_header_value("Content-Range"));
EXPECT_EQ(3, std::stoi(res->get_header_value("Content-Length")));
EXPECT_EQ("est", res->body);
}
TEST_F(ServerTest, GetFileContentWithContentType) {
auto res = cli_.Get("/file_content_with_content_type");
ASSERT_TRUE(res);
@@ -3692,12 +3839,27 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res = cli_.Get(
"/with-range",
{{"Range",
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())}});
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());
{{"Range", "bytes=0-" + std::to_string(std::numeric_limits<long>::max())},
{"Accept-Encoding", ""}});
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);
}
TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
auto res = cli_.Get("/with-range", {
{"Range", "bytes=0-"},
{"Accept-Encoding", ""},
});
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);
}
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
@@ -3787,7 +3949,10 @@ TEST_F(ServerTest, ClientStop) {
}
TEST_F(ServerTest, GetWithRange1) {
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
auto res = cli_.Get("/with-range", {
make_range_header({{3, 5}}),
{"Accept-Encoding", ""},
});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("3", res->get_header_value("Content-Length"));
@@ -3797,7 +3962,10 @@ TEST_F(ServerTest, GetWithRange1) {
}
TEST_F(ServerTest, GetWithRange2) {
auto res = cli_.Get("/with-range", {{make_range_header({{1, -1}})}});
auto res = cli_.Get("/with-range", {
make_range_header({{1, -1}}),
{"Accept-Encoding", ""},
});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("6", res->get_header_value("Content-Length"));
@@ -3807,7 +3975,10 @@ TEST_F(ServerTest, GetWithRange2) {
}
TEST_F(ServerTest, GetWithRange3) {
auto res = cli_.Get("/with-range", {{make_range_header({{0, 0}})}});
auto res = cli_.Get("/with-range", {
make_range_header({{0, 0}}),
{"Accept-Encoding", ""},
});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("1", res->get_header_value("Content-Length"));
@@ -3817,7 +3988,10 @@ TEST_F(ServerTest, GetWithRange3) {
}
TEST_F(ServerTest, GetWithRange4) {
auto res = cli_.Get("/with-range", {{make_range_header({{-1, 2}})}});
auto res = cli_.Get("/with-range", {
make_range_header({{-1, 2}}),
{"Accept-Encoding", ""},
});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("2", res->get_header_value("Content-Length"));
@@ -4131,7 +4305,10 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
// The compressed size should be less than a 10th of the original. May vary
// depending on the zlib library.
EXPECT_LT(res.get_request_header_value_u64("Content-Length"),
static_cast<uint64_t>(10 * 1024 * 1024));
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
}
@@ -4562,7 +4739,9 @@ TEST_F(ServerTest, Gzip) {
}
TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
auto res = cli_.Get("/compress");
Headers headers;
headers.emplace("Accept-Encoding", "");
auto res = cli_.Get("/compress", headers);
ASSERT_TRUE(res);
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
@@ -4611,12 +4790,16 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
}
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
Headers headers;
headers.emplace("Accept-Encoding", "");
std::string body;
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
auto res = cli_.Get("/compress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
@@ -4753,8 +4936,10 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
"Connection: close\r\n"
"\r\n";
ASSERT_TRUE(send_request(5, req));
EXPECT_EQ(header_value, "\v bar \x1B");
std::string res;
ASSERT_TRUE(send_request(5, req, &res));
EXPECT_EQ(header_value, "");
EXPECT_EQ("HTTP/1.1 400 Bad Request", res.substr(0, 24));
}
// Sends a raw request and verifies that there isn't a crash or exception.
@@ -4912,6 +5097,23 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, InvalidFieldValueContains_LF) {
std::string out;
std::string request(
"GET /header_field_value_check HTTP/1.1\r\nTest: [\n\n\n]\r\n\r\n", 55);
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, EmptyFieldValue) {
std::string out;
test_raw_request("GET /header_field_value_check HTTP/1.1\r\n"
"Test: \r\n\r\n",
&out);
EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15));
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;
@@ -5151,18 +5353,9 @@ TEST(MountTest, Redicect) {
EXPECT_EQ(StatusCode::OK_200, res->status);
}
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
TEST(ExceptionTest, ThrowExceptionInHandler) {
TEST(MountTest, MultibytesPathName) {
Server svr;
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception...");
});
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception\r\n...");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
@@ -5170,27 +5363,16 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
ASSERT_FALSE(svr.is_running());
});
svr.set_mount_point("/", "./www");
svr.wait_until_ready();
Client cli("localhost", PORT);
{
auto res = cli.Get("/exception");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
}
{
auto res = cli.Get("/unknown");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
}
auto res = cli.Get(u8"/日本語Dir/日本語File.txt");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(u8"日本語コンテンツ", res->body);
}
#endif
TEST(KeepAliveTest, ReadTimeout) {
Server svr;
@@ -5258,6 +5440,41 @@ TEST(KeepAliveTest, Issue1041) {
EXPECT_EQ(StatusCode::OK_200, result->status);
}
TEST(KeepAliveTest, Issue1959) {
Server svr;
svr.set_keep_alive_timeout(5);
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
res.set_content("a", "text/plain");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
if (!svr.is_running()) return;
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli("localhost", PORT);
cli.set_keep_alive(true);
using namespace std::chrono;
auto start = steady_clock::now();
cli.Get("/a");
svr.stop();
listen_thread.join();
auto end = steady_clock::now();
auto elapsed = duration_cast<milliseconds>(end - start).count();
EXPECT_LT(elapsed, 5000);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, SSLClientReconnection) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
@@ -5452,6 +5669,7 @@ TEST(LongPollingTest, ClientCloseDetection) {
auto count = 10;
while (count > 0 && sink.is_writable()) {
this_thread::sleep_for(chrono::milliseconds(10));
count--;
}
EXPECT_FALSE(sink.is_writable()); // the socket is closed
return true;
@@ -5971,6 +6189,18 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
ASSERT_EQ(StatusCode::OK_200, res->status);
}
TEST(SSLClientTest, Issue2004) {
Client client("https://google.com");
client.set_follow_location(true);
auto res = client.Get("/");
ASSERT_TRUE(res);
ASSERT_EQ(StatusCode::OK_200, res->status);
auto body = res->body;
EXPECT_EQ(body.substr(0, 15), "<!doctype html>");
}
#if 0
TEST(SSLClientTest, SetInterfaceWithINET6) {
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
@@ -6382,6 +6612,40 @@ TEST(SendAPI, SimpleInterface_Online) {
EXPECT_EQ(StatusCode::MovedPermanently_301, res->status);
}
TEST(SendAPI, WithParamsInRequest) {
Server svr;
svr.Get("/", [&](const Request &req, Response & /*res*/) {
EXPECT_TRUE(req.has_param("test"));
EXPECT_EQ("test_value", req.get_param_value("test"));
});
auto t = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
{
Request req;
req.method = "GET";
req.path = "/";
req.params.emplace("test", "test_value");
auto res = cli.send(req);
ASSERT_TRUE(res);
}
{
auto res = cli.Get("/", {{"test", "test_value"}}, Headers{});
ASSERT_TRUE(res);
}
}
TEST(ClientImplMethods, GetSocketTest) {
httplib::Server svr;
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
@@ -7277,9 +7541,9 @@ TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
"text2"
"\r\n------------";
std::string resonse;
ASSERT_TRUE(send_request(1, req, &resonse));
ASSERT_EQ("200", resonse.substr(9, 3));
std::string response;
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("200", response.substr(9, 3));
}
TEST(MultipartFormDataTest, ContentLength) {
@@ -7324,11 +7588,10 @@ TEST(MultipartFormDataTest, ContentLength) {
"text2"
"\r\n------------\r\n";
std::string resonse;
ASSERT_TRUE(send_request(1, req, &resonse));
ASSERT_EQ("200", resonse.substr(9, 3));
std::string response;
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("200", response.substr(9, 3));
}
#endif
TEST(TaskQueueTest, IncreaseAtomicInteger) {
@@ -7706,6 +7969,114 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
}
TEST(InvalidHeaderCharsTest, is_field_name) {
EXPECT_TRUE(detail::fields::is_field_name("exampleToken"));
EXPECT_TRUE(detail::fields::is_field_name("token123"));
EXPECT_TRUE(detail::fields::is_field_name("!#$%&'*+-.^_`|~"));
EXPECT_FALSE(detail::fields::is_field_name("example token"));
EXPECT_FALSE(detail::fields::is_field_name(" example_token"));
EXPECT_FALSE(detail::fields::is_field_name("example_token "));
EXPECT_FALSE(detail::fields::is_field_name("token@123"));
EXPECT_FALSE(detail::fields::is_field_name(""));
EXPECT_FALSE(detail::fields::is_field_name("example\rtoken"));
EXPECT_FALSE(detail::fields::is_field_name("example\ntoken"));
EXPECT_FALSE(detail::fields::is_field_name(std::string("\0", 1)));
EXPECT_FALSE(detail::fields::is_field_name("example\ttoken"));
}
TEST(InvalidHeaderCharsTest, is_field_value) {
EXPECT_TRUE(detail::fields::is_field_value("exampleToken"));
EXPECT_TRUE(detail::fields::is_field_value("token123"));
EXPECT_TRUE(detail::fields::is_field_value("!#$%&'*+-.^_`|~"));
EXPECT_TRUE(detail::fields::is_field_value("example token"));
EXPECT_FALSE(detail::fields::is_field_value(" example_token"));
EXPECT_FALSE(detail::fields::is_field_value("example_token "));
EXPECT_TRUE(detail::fields::is_field_value("token@123"));
EXPECT_TRUE(detail::fields::is_field_value(""));
EXPECT_FALSE(detail::fields::is_field_value("example\rtoken"));
EXPECT_FALSE(detail::fields::is_field_value("example\ntoken"));
EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1)));
EXPECT_TRUE(detail::fields::is_field_value("example\ttoken"));
EXPECT_TRUE(detail::fields::is_field_value("0"));
}
TEST(InvalidHeaderCharsTest, OnServer) {
Server svr;
svr.Get("/test_name", [&](const Request &req, Response &res) {
std::string header = "Not Set";
if (req.has_param("header")) { header = req.get_param_value("header"); }
res.set_header(header, "value");
res.set_content("Page Content Page Content", "text/plain");
});
svr.Get("/test_value", [&](const Request &req, Response &res) {
std::string header = "Not Set";
if (req.has_param("header")) { header = req.get_param_value("header"); }
res.set_header("X-Test", header);
res.set_content("Page Content Page Content", "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);
{
auto res = cli.Get(
R"(/test_name?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)");
ASSERT_TRUE(res);
EXPECT_EQ("Page Content Page Content", res->body);
EXPECT_FALSE(res->has_header("HEADER_KEY"));
}
{
auto res = cli.Get(
R"(/test_value?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)");
ASSERT_TRUE(res);
EXPECT_EQ("Page Content Page Content", res->body);
EXPECT_FALSE(res->has_header("HEADER_KEY"));
}
}
TEST(InvalidHeaderValueTest, InvalidContentLength) {
auto handled = false;
Server svr;
svr.Post("/test", [&](const Request &, Response &) { handled = true; });
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
ASSERT_FALSE(handled);
});
svr.wait_until_ready();
auto req = "POST /test HTTP/1.1\r\n"
"Content-Length: x\r\n"
"\r\n";
std::string response;
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("HTTP/1.1 400 Bad Request",
response.substr(0, response.find("\r\n")));
}
#ifndef _WIN32
TEST(Expect100ContinueTest, ServerClosesConnection) {
static constexpr char reject[] = "Unauthorized";
+5 -5
View File
@@ -28,26 +28,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -177,4 +177,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>
+7
View File
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2024 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
configure_file(input: 'empty_file', output: 'empty_file', copy: true)
configure_file(input: 'file', output: 'file', copy: true)
subdir('dir')
+1
View File
@@ -0,0 +1 @@
日本語コンテンツ