Compare commits

...

39 Commits

Author SHA1 Message Date
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
David Pfahler d87d0672a8 Split the header only if needed (#1060)
* Update split.py

* Update split.py

* Update split.py

* Update split.py
2021-09-28 18:11:50 -04:00
estshorter 3da42fd1e8 Avoid min/max macro expansion on Windows (#1057) 2021-09-25 08:53:15 -04:00
yhirose 503aa61325 Fix problem with an empty parameter in set_base_dir 2021-09-20 17:40:05 -04:00
null e4c276d0c2 doc: fix typo in README (#1056)
fixed typo in README.md, replacing `Sutudio` with `Studio`.
2021-09-18 11:33:23 -04:00
yhirose e07f7691a8 Update README 2021-09-17 21:26:31 -04:00
yhirose 623ab4a96e Updated README regarding Visual Studio support 2021-09-17 11:36:08 -04:00
Zizheng Tai e1efa337a2 Make Client move-constructible (#1051) 2021-09-16 14:05:42 -04:00
Andrea Pappacoda 549cdf2f7d test: avoid infinite loop when IPV6 is unsupported (#1054) 2021-09-16 14:04:43 -04:00
yhirose 3c522386e9 Fix "Issue 38551 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzze" 2021-09-12 19:24:48 -04:00
yhirose c202aa9ce9 Read buffer support. (Fix #1023) (#1046) 2021-09-12 00:26:02 -04:00
Andrea Pappacoda e3e28c6231 meson: add tests (#1044)
This integrates the "main" test suite (test/test.cc) in Meson.

This allows to run the tests in the CI with the Meson-built version of
the library to ensure that nothing breaks unexpectedly.

It also simplifies life of downstream packagers, that do not have to
write a custom build script to split the library and run tests but can
instead just let Meson do that for them.
2021-09-11 14:26:48 -04:00
yhirose 4e05368086 Fix #1054 2021-09-11 14:13:49 -04:00
yhirose e1afe74fe2 Fix #1037 2021-09-10 22:42:14 -04:00
yhirose 461acb02f5 Comment out SlowPostFail test for now 2021-09-10 22:37:31 -04:00
Gregor Jasny 415edc237c Set error variable for failed write_data (#1036) 2021-09-05 16:15:46 -04:00
Andrea Pappacoda e20ecd2574 Full Meson support (#1033)
* Full Meson support
cpp-httplib can be now built with Meson even in compiled library mode.

The library is built with LTO, supports OpenSSL, zlib and Brotli,
and the build system also generates a pkg-config file when needed.

Compared to the CMake file this one is quite small (more than five times
smaller!), and maintaining it won't be an issue :)

* meson: automatic versioning
2021-09-04 11:33:53 -04:00
yhirose ab477b5631 Fix "Issue 37742 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-09-02 22:57:57 -04:00
yhirose 0823d5c7f2 Fixed #1031 2021-08-30 17:16:31 -04:00
yhirose 1cc6930363 Append '_Online' suffix to Unit test names that access external servers 2021-08-23 13:02:19 -04:00
yhirose 4297500928 Fix #1024 2021-08-17 09:28:17 -04:00
Thomas Behn a58f042614 Don't define INVALID_SOCKET if it has been defined already (i.e. by libpcap) (#1021)
Co-authored-by: Thomas Behn <Thomas.Behn@meinberg.de>
2021-08-10 08:29:27 -04:00
yhirose 469c6bc2b6 Fix #1017 2021-08-02 15:44:50 -04:00
Joel Rosdahl 887074efd2 Add test of httplib.h split into .h + .cc (#1015)
In order to test the split version (.h + .cc via split.py):

- Added a test_split program in the test directory whose main purpose is
  to verify that it works to compile and link the test case code against
  the split httplib.h version.
- Moved types needed for test cases to the “header part” of httplib.h.
  Also added forward declarations of functions needed by test cases.
- Added an include_httplib.cc file which is linked together with test.cc
  to verify that inline keywords have not been forgotten.

The changes to httplib.h just move code around (or add forward
declarations), with one exception: detail::split and
detail::process_client_socket have been converted to non-template
functions (taking an std::function instead of using a type parameter for
the function) and forward-declared instead. This avoids having to move
the templates to the “header part”.
2021-07-31 09:53:30 -04:00
Joel Rosdahl 9c2c15ca45 Add missing template method implementations (#1013)
When using the split version of httplib.h the templated implementation
of e.g. Client::set_connection_timeout ends up in httplib.cc and
therefore results in a linker error since the needed template
specialization has not been instantiated. Fix this by moving the
implementation of template methods into the part that ends up in
httplib.h after the split.

Fixes #1008.
2021-07-30 10:05:49 -04:00
Joel Rosdahl 1b3b098329 Avoid hardcoded ports in RedirectToDifferentPort.Redirect test (#1012)
The RedirectToDifferentPort.Redirect test assumes that port 8080 and
8081 are available on localhost. They aren’t on my system so the test
fails. Improve this by binding to available ports instead of hardcoded
ones.
2021-07-30 10:04:02 -04:00
Joel Rosdahl 6f7075e3aa Improve split script (#1011)
- Added shebang and made the script executable.
- Added help text.
- Added -o/--out argument for specifying output directory.
- Added -e/--extension argument for specifying file extension of the
  implementation file.
- Made the script find httplib.h next to split.py instead of the current
  working directory. This makes it possible to call the script from
  another directory.
- Simplified code structure slightly.
- Improved variable naming to follow Python conventions.
2021-07-30 10:03:05 -04:00
yhirose ccbddd8842 Allow LargeRandomData test only on Windows 2021-07-22 22:17:31 -04:00
yosh-matsuda 879dd261c2 Fix gzip compression/decompression over 4 GiB data size (#1002)
* Fix gzip compression/decompression over 4 GiB data size

* Add gzip test for large random data
2021-07-22 22:07:40 -04:00
xxrl 52f5eb5980 [Fix] ca_cert_path/ce_cert_store lose (#1004)
When redirect from http to https, user setting for ca_cert will lose

issue: #1003
2021-07-22 21:41:41 -04:00
Gregor Jasny ea2f69a0d7 Add httplib::Error to std::string function (#999)
Fixes: #978
2021-07-19 21:17:44 -04:00
Gregor Jasny 9f2064a8ed Fix remaining test warnings (#1001)
* Use portable way to encode ESC

'\e' is a GNU extension

* Use length specifier for size_t
2021-07-19 21:17:18 -04:00
yhirose e3750d9ddf Simplefied server APIs 2021-07-17 18:09:30 -04:00
yhirose c1eee3012e Fix #998 2021-07-17 17:18:56 -04:00
Gregor Jasny 6b08babbd2 Use googletest 1.11.0 (#1000)
* Update googletest to version 1.11.0

* Fix test warnings
2021-07-17 13:21:03 -04:00
19 changed files with 14193 additions and 17228 deletions
+54 -2
View File
@@ -26,10 +26,10 @@ jobs:
run: brew install brotli
- name: make
if: matrix.os != 'windows-latest'
run: cd test && make
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,3 +39,55 @@ 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
+3
View File
@@ -10,8 +10,11 @@ example/redirect
example/sse*
example/upload
example/*.pem
test/httplib.cc
test/httplib.h
test/test
test/test_proxy
test/test_split
test/test.xcodeproj/xcuser*
test/test.xcodeproj/*/xcuser*
test/*.pem
+23 -5
View File
@@ -256,6 +256,7 @@ svr.Post("/multipart", [&](const auto& req, auto& res) {
svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
// NOTE: `content_reader` is blocking until every form data field is read
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
@@ -272,7 +273,6 @@ svr.Post("/content_receiver",
body.append(data, data_length);
return true;
});
res.set_content(body, "text/plain");
}
});
```
@@ -746,13 +746,29 @@ 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
-------------------------------
```bash
> python3 split.py
> ls out
httplib.h httplib.cc
```console
$ ./split.py -h
usage: split.py [-h] [-e EXTENSION] [-o OUT]
This script splits httplib.h into .h and .cc parts.
optional arguments:
-h, --help show this help message and exit
-e EXTENSION, --extension EXTENSION
extension of the implementation file (default: cc)
-o OUT, --out OUT where to write the files (default: out)
$ ./split.py
Wrote out/httplib.h and out/httplib.cc
```
NOTE
@@ -777,6 +793,8 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
#include <httplib.h>
```
Note: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
Note: Windows 8 or lower and Cygwin on Windows are not supported.
License
+841 -586
View File
File diff suppressed because it is too large Load Diff
+108 -2
View File
@@ -1,7 +1,113 @@
project('cpp-httplib', 'cpp', license: 'MIT')
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'))
project(
'cpp-httplib',
'cpp',
license: 'MIT',
default_options: [
'cpp_std=c++11',
'buildtype=release',
'b_ndebug=if-release',
'b_lto=true',
'warning_level=3'
],
meson_version: '>=0.47.0'
)
# Check just in case downstream decides to edit the source
# and add a project version
version = meson.project_version()
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()
endif
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'))
if openssl_dep.found()
deps += openssl_dep
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
endif
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
if zlib_dep.found()
deps += zlib_dep
args += '-DCPPHTTPLIB_ZLIB_SUPPORT'
endif
brotli_deps = [dependency('libbrotlicommon', required: get_option('cpp-httplib_brotli'))]
brotli_deps += dependency('libbrotlidec', required: get_option('cpp-httplib_brotli'))
brotli_deps += dependency('libbrotlienc', required: get_option('cpp-httplib_brotli'))
brotli_found_all = true
foreach brotli_dep : brotli_deps
if not brotli_dep.found()
brotli_found_all = false
endif
endforeach
if brotli_found_all
deps += brotli_deps
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
endif
cpp_httplib_dep = dependency('', required: false)
if get_option('cpp-httplib_compile')
httplib_ch = custom_target(
'split',
input: 'httplib.h',
output: ['httplib.cc', 'httplib.h'],
command: [python, files('split.py'), '--out', meson.current_build_dir()],
install: true,
install_dir: [false, get_option('includedir')]
)
lib = library(
'cpp-httplib',
sources: httplib_ch,
dependencies: deps,
cpp_args: args,
version: version,
install: true
)
cpp_httplib_dep = declare_dependency(link_with: lib, sources: httplib_ch[1])
import('pkgconfig').generate(
lib,
description: 'A C++ HTTP/HTTPS server and client library',
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)
endif
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
endif
if get_option('cpp-httplib_test')
subdir('test')
endif
+9
View File
@@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
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_test', type: 'boolean', value: false, description: 'Build tests')
Regular → Executable
+60 -25
View File
@@ -1,32 +1,67 @@
#!/usr/bin/env python3
"""This script splits httplib.h into .h and .cc parts."""
import argparse
import os
import sys
border = '// ----------------------------------------------------------------------------'
PythonVersion = sys.version_info[0];
args_parser = argparse.ArgumentParser(description=__doc__)
args_parser.add_argument(
"-e", "--extension", help="extension of the implementation file (default: cc)",
default="cc"
)
args_parser.add_argument(
"-o", "--out", help="where to write the files (default: out)", default="out"
)
args = args_parser.parse_args()
with open('httplib.h') as f:
lines = f.readlines()
inImplementation = False
if PythonVersion < 3:
os.makedirs('out')
cur_dir = os.path.dirname(sys.argv[0])
lib_name = 'httplib'
header_name = '/' + lib_name + '.h'
source_name = '/' + lib_name + '.' + args.extension
# get the input file
in_file = cur_dir + header_name
# get the output file
h_out = args.out + header_name
cc_out = args.out + source_name
# if the modification time of the out file is after the in file,
# don't split (as it is already finished)
do_split = True
if os.path.exists(h_out):
in_time = os.path.getmtime(in_file)
out_time = os.path.getmtime(h_out)
do_split = in_time > out_time
if do_split:
with open(in_file) as f:
lines = f.readlines()
python_version = sys.version_info[0]
if python_version < 3:
os.makedirs(args.out)
else:
os.makedirs('out', exist_ok=True)
with open('out/httplib.h', 'w') as fh:
with open('out/httplib.cc', 'w') as fc:
fc.write('#include "httplib.h"\n')
fc.write('namespace httplib {\n')
for line in lines:
isBorderLine = border in line
if isBorderLine:
inImplementation = not inImplementation
else:
if inImplementation:
fc.write(line.replace('inline ', ''))
pass
else:
fh.write(line)
pass
fc.write('} // namespace httplib\n')
os.makedirs(args.out, exist_ok=True)
in_implementation = False
cc_out = args.out + source_name
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
fc.write('#include "httplib.h"\n')
fc.write('namespace httplib {\n')
for line in lines:
is_border_line = border in line
if is_border_line:
in_implementation = not in_implementation
elif in_implementation:
fc.write(line.replace('inline ', ''))
else:
fh.write(line)
fc.write('} // namespace httplib\n')
print("Wrote {} and {}".format(h_out, cc_out))
else:
print("{} and {} are up to date".format(h_out, cc_out))
+38 -6
View File
@@ -1,5 +1,5 @@
#CXX = clang++
CXXFLAGS = -g -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
OPENSSL_DIR = /usr/local/opt/openssl@1.1
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
@@ -9,17 +9,49 @@ 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
all : test
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
proxy : test_proxy
./test_proxy
test : test.cc ../httplib.h Makefile cert.pem
$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
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 test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
$(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 .
cert.pem:
openssl genrsa 2048 > key.pem
@@ -32,4 +64,4 @@ cert.pem:
#c_rehash .
clean:
rm -f test test_proxy pem *.0 *.1 *.srl
rm -f test 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
@@ -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;
}
}
+5790 -2421
View File
File diff suppressed because it is too large Load Diff
+6845 -14039
View File
File diff suppressed because it is too large Load Diff
+19 -4
View File
@@ -27,13 +27,28 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <iostream>
#include <cstdio>
#include "gtest/gtest.h"
GTEST_API_ int main(int argc, char **argv) {
std::cout << "Running main() from gtest_main.cc\n";
#if GTEST_OS_ESP8266 || GTEST_OS_ESP32
#if GTEST_OS_ESP8266
extern "C" {
#endif
void setup() {
testing::InitGoogleTest();
}
void loop() { RUN_ALL_TESTS(); }
#if GTEST_OS_ESP8266
}
#endif
#else
GTEST_API_ int main(int argc, char **argv) {
printf("Running main() from %s\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#endif
+5
View File
@@ -0,0 +1,5 @@
// The sole purpose of this file is to include httplib.h in a separate
// compilation unit, thus verifying that inline keywords have not been forgotten
// when linked together with test.cc.
#include <httplib.h>
+97
View File
@@ -0,0 +1,97 @@
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
gtest_dep = dependency('gtest', main: true)
openssl = find_program('openssl')
test_conf = files('test.conf')
key_pem = custom_target(
'key_pem',
output: 'key.pem',
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
)
temp_req = custom_target(
'temp_req',
input: key_pem,
output: 'temp_req',
command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-out', '@OUTPUT@']
)
cert_pem = custom_target(
'cert_pem',
input: [temp_req, key_pem],
output: 'cert.pem',
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '3650', '-req', '-signkey', '@INPUT1@', '-out', '@OUTPUT@']
)
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']
)
rootca_key_pem = custom_target(
'rootca_key_pem',
output: 'rootCA.key.pem',
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
)
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@']
)
client_key_pem = custom_target(
'client_key_pem',
output: 'client.key.pem',
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
)
client_temp_req = custom_target(
'client_temp_req',
input: client_key_pem,
output: 'client_temp_req',
command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-out', '@OUTPUT@']
)
client_cert_pem = custom_target(
'client_cert_pem',
input: [client_temp_req, rootca_cert_pem, rootca_key_pem],
output: 'client.cert.pem',
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@']
)
# Copy test files to the build directory
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
subdir(join_paths('www', 'dir'))
subdir(join_paths('www2', 'dir'))
subdir(join_paths('www3', 'dir'))
test(
'main',
executable(
'main',
'test.cc',
dependencies: [
cpp_httplib_dep,
gtest_dep
]
),
depends: [
key_pem,
cert_pem,
cert2_pem,
rootca_key_pem,
rootca_cert_pem,
client_key_pem,
client_cert_pem
],
workdir: meson.current_build_dir(),
timeout: 300
)
+279 -99
View File
@@ -8,6 +8,7 @@
#include <sstream>
#include <stdexcept>
#include <thread>
#include <type_traits>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_CERT2_FILE "./cert2.pem"
@@ -40,6 +41,11 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
throw std::runtime_error("invalid mulitpart form data name error");
}
TEST(ConstructorTest, MoveConstructible) {
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
}
#ifdef _WIN32
TEST(StartupTest, WSAStartup) {
WSADATA wsaData;
@@ -172,7 +178,7 @@ TEST(GetHeaderValueTest, SetContent) {
EXPECT_EQ("text/html", res.get_header_value("Content-Type"));
res.set_content("text", "text/plain");
EXPECT_EQ(1, res.get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res.get_header_value_count("Content-Type"));
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
}
@@ -354,7 +360,7 @@ TEST(BufferStreamTest, read) {
EXPECT_EQ(0, strm.read(buf, 1));
}
TEST(ChunkedEncodingTest, FromHTTPWatch) {
TEST(ChunkedEncodingTest, FromHTTPWatch_Online) {
auto host = "www.httpwatch.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -377,7 +383,7 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) {
EXPECT_EQ(out, res->body);
}
TEST(ChunkedEncodingTest, WithContentReceiver) {
TEST(ChunkedEncodingTest, WithContentReceiver_Online) {
auto host = "www.httpwatch.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -405,7 +411,7 @@ TEST(ChunkedEncodingTest, WithContentReceiver) {
EXPECT_EQ(out, body);
}
TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
auto host = "www.httpwatch.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -437,7 +443,7 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
EXPECT_EQ(out, body);
}
TEST(RangeTest, FromHTTPBin) {
TEST(RangeTest, FromHTTPBin_Online) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -542,7 +548,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
ASSERT_TRUE(!res);
stringstream s;
s << "error code: " << res.error();
EXPECT_EQ("error code: 2", s.str());
EXPECT_EQ("error code: Connection (2)", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
@@ -578,7 +584,7 @@ TEST(ConnectionErrorTest, Timeout) {
EXPECT_TRUE(res.error() == Error::Connection);
}
TEST(CancelTest, NoCancel) {
TEST(CancelTest, NoCancel_Online) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -596,7 +602,7 @@ TEST(CancelTest, NoCancel) {
EXPECT_EQ(200, res->status);
}
TEST(CancelTest, WithCancelSmallPayload) {
TEST(CancelTest, WithCancelSmallPayload_Online) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -613,7 +619,7 @@ TEST(CancelTest, WithCancelSmallPayload) {
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayload) {
TEST(CancelTest, WithCancelLargePayload_Online) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -632,7 +638,7 @@ TEST(CancelTest, WithCancelLargePayload) {
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(BaseAuthTest, FromHTTPWatch) {
TEST(BaseAuthTest, FromHTTPWatch_Online) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -683,7 +689,7 @@ TEST(BaseAuthTest, FromHTTPWatch) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(DigestAuthTest, FromHTTPWatch) {
TEST(DigestAuthTest, FromHTTPWatch_Online) {
auto host = "httpbin.org";
auto port = 443;
SSLClient cli(host, port);
@@ -730,7 +736,7 @@ TEST(DigestAuthTest, FromHTTPWatch) {
}
#endif
TEST(AbsoluteRedirectTest, Redirect) {
TEST(AbsoluteRedirectTest, Redirect_Online) {
auto host = "nghttp2.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -745,7 +751,7 @@ TEST(AbsoluteRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(RedirectTest, Redirect) {
TEST(RedirectTest, Redirect_Online) {
auto host = "nghttp2.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -760,7 +766,7 @@ TEST(RedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(RelativeRedirectTest, Redirect) {
TEST(RelativeRedirectTest, Redirect_Online) {
auto host = "nghttp2.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -775,7 +781,7 @@ TEST(RelativeRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(TooManyRedirectTest, Redirect) {
TEST(TooManyRedirectTest, Redirect_Online) {
auto host = "nghttp2.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -791,7 +797,7 @@ TEST(TooManyRedirectTest, Redirect) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(YahooRedirectTest, Redirect) {
TEST(YahooRedirectTest, Redirect_Online) {
Client cli("yahoo.com");
auto res = cli.Get("/");
@@ -805,7 +811,7 @@ TEST(YahooRedirectTest, Redirect) {
EXPECT_EQ("https://yahoo.com/", res->location);
}
TEST(HttpsToHttpRedirectTest, Redirect) {
TEST(HttpsToHttpRedirectTest, Redirect_Online) {
SSLClient cli("nghttp2.org");
cli.set_follow_location(true);
auto res = cli.Get(
@@ -814,7 +820,7 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, Redirect) {
TEST(HttpsToHttpRedirectTest2, Redirect_Online) {
SSLClient cli("nghttp2.org");
cli.set_follow_location(true);
@@ -827,7 +833,7 @@ TEST(HttpsToHttpRedirectTest2, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest3, Redirect) {
TEST(HttpsToHttpRedirectTest3, Redirect_Online) {
SSLClient cli("nghttp2.org");
cli.set_follow_location(true);
@@ -839,55 +845,62 @@ TEST(HttpsToHttpRedirectTest3, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(UrlWithSpace, Redirect) {
TEST(UrlWithSpace, Redirect_Online) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr8080;
Server svr8081;
svr8080.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_redirect("http://localhost:8081/2");
});
svr8081.Get("/2", [&](const Request & /*req*/, Response &res) {
Server svr1;
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread8080 = std::thread([&]() { svr8080.listen("localhost", 8080); });
int svr1_port = 0;
auto thread1 = std::thread([&]() {
svr1_port = svr1.bind_to_any_port("localhost");
svr1.listen_after_bind();
});
auto thread8081 = std::thread([&]() { svr8081.listen("localhost", 8081); });
Server svr2;
svr2.Get("/2", [&](const Request & /*req*/, Response &res) {
res.set_redirect("http://localhost:" + std::to_string(svr1_port) + "/1");
});
while (!svr8080.is_running() || !svr8081.is_running()) {
int svr2_port = 0;
auto thread2 = std::thread([&]() {
svr2_port = svr2.bind_to_any_port("localhost");
svr2.listen_after_bind();
});
while (!svr1.is_running() || !svr2.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", 8080);
Client cli("localhost", svr2_port);
cli.set_follow_location(true);
auto res = cli.Get("/1");
auto res = cli.Get("/2");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!", res->body);
svr8080.stop();
svr8081.stop();
thread8080.join();
thread8081.join();
ASSERT_FALSE(svr8080.is_running());
ASSERT_FALSE(svr8081.is_running());
svr1.stop();
svr2.stop();
thread1.join();
thread2.join();
ASSERT_FALSE(svr1.is_running());
ASSERT_FALSE(svr2.is_running());
}
TEST(RedirectFromPageWithContent, Redirect) {
@@ -954,14 +967,25 @@ TEST(RedirectFromPageWithContentIP6, Redirect) {
res.set_redirect("http://[::1]:1234/2");
});
svr.Get("/2", [&](const Request & /*req*/, Response &res) {
svr.Get("/2", [&](const Request &req, Response &res) {
auto host_header = req.headers.find("Host");
ASSERT_TRUE(host_header != req.headers.end());
EXPECT_EQ("[::1]:1234", host_header->second);
res.set_content("Hello World!", "text/plain");
});
auto th = std::thread([&]() { svr.listen("::1", 1234); });
while (!svr.is_running()) {
// When IPV6 support isn't available svr.listen("::1", 1234) never
// actually starts anything, so the condition !svr.is_running() will
// always remain true, and the loop never stops.
// This basically counts how many milliseconds have passed since the
// call to svr.listen(), and if after 5 seconds nothing started yet
// aborts the test.
for (unsigned int milliseconds = 0; !svr.is_running(); milliseconds++) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ASSERT_LT(milliseconds, 5000U);
}
// Give GET time to get a few messages.
@@ -1222,9 +1246,9 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Routing Handler", res->body);
EXPECT_EQ(1, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ("on", res->get_header_value("PRE_ROUTING"));
EXPECT_EQ(1, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ("on", res->get_header_value("POST_ROUTING"));
}
@@ -1235,8 +1259,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!\n", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
{
@@ -1246,8 +1270,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(404, res->status);
EXPECT_EQ("Error", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
svr.stop();
@@ -1306,31 +1330,31 @@ protected:
.Get("/http_response_splitting",
[&](const Request & /*req*/, Response &res) {
res.set_header("a", "1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\rSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\r\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\rb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_redirect("1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("Location"));
})
.Get("/slow",
@@ -1338,11 +1362,13 @@ protected:
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
#if 0
.Post("/slowpost",
[&](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
#endif
.Get("/remote_addr",
[&](const Request &req, Response &res) {
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
@@ -1648,7 +1674,7 @@ protected:
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
EXPECT_EQ(data_length, 7);
EXPECT_EQ(7U, data_length);
body.append(data, data_length);
return true;
});
@@ -1696,7 +1722,7 @@ protected:
})
.Post("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1704,7 +1730,7 @@ protected:
})
.Put("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1712,7 +1738,7 @@ protected:
})
.Patch("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1720,7 +1746,7 @@ protected:
})
.Delete("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1800,7 +1826,7 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ(200, res->status);
EXPECT_EQ("OK", res->reason);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -1810,7 +1836,7 @@ TEST_F(ServerTest, GetMethod200withPercentEncoding) {
EXPECT_EQ("HTTP/1.1", res->version);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -2088,25 +2114,25 @@ TEST_F(ServerTest, Binary) {
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, BinaryString) {
@@ -2115,22 +2141,22 @@ TEST_F(ServerTest, BinaryString) {
auto res = cli_.Post("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, EmptyRequest) {
@@ -2446,7 +2472,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
@@ -2533,7 +2559,7 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
@@ -2612,6 +2638,7 @@ TEST_F(ServerTest, SlowRequest) {
std::thread([=]() { auto res = cli_.Get("/slow"); }));
}
#if 0
TEST_F(ServerTest, SlowPost) {
char buffer[64 * 1024];
memset(buffer, 0x42, sizeof(buffer));
@@ -2645,6 +2672,7 @@ TEST_F(ServerTest, SlowPostFail) {
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Write, res.error());
}
#endif
TEST_F(ServerTest, Put) {
auto res = cli_.Put("/put", "PUT", "text/plain");
@@ -2864,6 +2892,54 @@ TEST(GzipDecompressor, ChunkedDecompression) {
}
ASSERT_EQ(data, decompressed_data);
}
#ifdef _WIN32
TEST(GzipDecompressor, LargeRandomData) {
// prepare large random data that is difficult to be compressed and is
// expected to have large size even when compressed
std::random_device seed_gen;
std::mt19937 random(seed_gen());
constexpr auto large_size_byte = 4294967296UL; // 4GiB
constexpr auto data_size = large_size_byte + 134217728UL; // + 128MiB
std::vector<std::uint32_t> data(data_size / sizeof(std::uint32_t));
std::generate(data.begin(), data.end(), [&]() { return random(); });
// compress data over 4GiB
std::string compressed_data;
compressed_data.reserve(large_size_byte + 536870912UL); // + 512MiB reserved
httplib::detail::gzip_compressor compressor;
auto result = compressor.compress(reinterpret_cast<const char *>(data.data()),
data.size() * sizeof(std::uint32_t), true,
[&](const char *data, size_t size) {
compressed_data.insert(
compressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// FIXME: compressed data size is expected to be greater than 4GiB,
// but there is no guarantee
// ASSERT_TRUE(compressed_data.size() >= large_size_byte);
// decompress data over 4GiB
std::string decompressed_data;
decompressed_data.reserve(data_size);
httplib::detail::gzip_decompressor decompressor;
result = decompressor.decompress(
compressed_data.data(), compressed_data.size(),
[&](const char *data, size_t size) {
decompressed_data.insert(decompressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// compare
ASSERT_EQ(data_size, decompressed_data.size());
ASSERT_TRUE(std::memcmp(data.data(), decompressed_data.data(), data_size) ==
0);
}
#endif
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
@@ -3124,7 +3200,7 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/compress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3150,14 +3226,14 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("33", res->get_header_value("Content-Length"));
EXPECT_EQ(33, res->body.size());
EXPECT_EQ(33U, res->body.size());
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
std::string body;
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3193,7 +3269,7 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/nocompress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3289,14 +3365,14 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
// Only space and horizontal tab are whitespace. Make sure other whitespace-
// like characters are not treated the same - use vertical tab and escape.
const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
"foo: \t \v bar \e\t \r\n"
"foo: \t \v bar \x1B\t \r\n"
"Connection: close\r\n"
"\r\n";
ASSERT_TRUE(send_request(5, req));
svr.stop();
t.join();
EXPECT_EQ(header_value, "\v bar \e");
EXPECT_EQ(header_value, "\v bar \x1B");
}
// Sends a raw request and verifies that there isn't a crash or exception.
@@ -3448,7 +3524,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
auto size = static_cast<size_t>(sprintf(buffer, "data:%zd\n\n", offset));
auto ret = sink.write(buffer, size);
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -3503,10 +3579,12 @@ TEST(StreamingTest, NoContentLengthStreaming) {
Client client(HOST, PORT);
auto get_thread = std::thread([&client]() {
auto res = client.Get("/stream", [](const char *data, size_t len) -> bool {
EXPECT_EQ("aaabbb", std::string(data, len));
std::string s;
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
s += std::string(data, len);
return true;
});
EXPECT_EQ("aaabbb", s);
});
// Give GET time to get a few messages.
@@ -3705,19 +3783,19 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
TEST(GetWithParametersTest, GetWithParameters) {
Server svr;
svr.Get("/", [&](const Request &req, Response &res) {
svr.Get("/", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
svr.Get("/params", [&](const Request &req, Response &res) {
svr.Get("/params", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
svr.Get(R"(/resources/([a-z0-9\\-]+))", [&](const Request& req, Response& res) {
svr.Get(R"(/resources/([a-z0-9\\-]+))", [&](const Request &req, Response &) {
EXPECT_EQ("resource-id", req.matches[1]);
EXPECT_EQ("foo", req.get_param_value("param1"));
EXPECT_EQ("bar", req.get_param_value("param2"));
@@ -3800,7 +3878,7 @@ TEST(GetWithParametersTest, GetWithParameters2) {
ASSERT_FALSE(svr.is_running());
}
TEST(ClientDefaultHeadersTest, DefaultHeaders) {
TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) {
Client cli("httpbin.org");
cli.set_default_headers({make_range_header({{1, 10}})});
cli.set_connection_timeout(5);
@@ -4037,21 +4115,21 @@ TEST(SSLClientTest, UpdateCAStore) {
httplib_client.set_ca_cert_store(ca_store_2);
}
TEST(SSLClientTest, ServerNameIndication) {
TEST(SSLClientTest, ServerNameIndication_Online) {
SSLClient cli("httpbin.org", 443);
auto res = cli.Get("/get");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification1) {
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
SSLClient cli("google.com");
auto res = cli.Get("/");
ASSERT_TRUE(res);
ASSERT_EQ(301, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification2) {
TEST(SSLClientTest, ServerCertificateVerification2_Online) {
SSLClient cli("google.com");
cli.enable_server_certificate_verification(true);
cli.set_ca_cert_path("hello");
@@ -4060,7 +4138,7 @@ TEST(SSLClientTest, ServerCertificateVerification2) {
EXPECT_EQ(Error::SSLLoadingCerts, res.error());
}
TEST(SSLClientTest, ServerCertificateVerification3) {
TEST(SSLClientTest, ServerCertificateVerification3_Online) {
SSLClient cli("google.com");
cli.set_ca_cert_path(CA_CERT_FILE);
auto res = cli.Get("/");
@@ -4093,7 +4171,7 @@ TEST(SSLClientTest, ServerCertificateVerification4) {
t.join();
}
TEST(SSLClientTest, WildcardHostNameMatch) {
TEST(SSLClientTest, WildcardHostNameMatch_Online) {
SSLClient cli("www.youtube.com");
cli.set_ca_cert_path(CA_CERT_FILE);
@@ -4314,6 +4392,75 @@ 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();
}
#endif
#ifdef _WIN32
@@ -4338,7 +4485,7 @@ TEST(NoScheme, SimpleInterface) {
ASSERT_TRUE(cli.is_valid());
}
TEST(SendAPI, SimpleInterface) {
TEST(SendAPI, SimpleInterface_Online) {
Client cli("http://yahoo.com");
Request req;
@@ -4351,7 +4498,7 @@ TEST(SendAPI, SimpleInterface) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(YahooRedirectTest2, SimpleInterface) {
TEST(YahooRedirectTest2, SimpleInterface_Online) {
Client cli("http://yahoo.com");
auto res = cli.Get("/");
@@ -4365,7 +4512,7 @@ TEST(YahooRedirectTest2, SimpleInterface) {
EXPECT_EQ("https://yahoo.com/", res->location);
}
TEST(YahooRedirectTest3, SimpleInterface) {
TEST(YahooRedirectTest3, SimpleInterface_Online) {
Client cli("https://yahoo.com");
auto res = cli.Get("/");
@@ -4379,7 +4526,7 @@ TEST(YahooRedirectTest3, SimpleInterface) {
EXPECT_EQ("https://www.yahoo.com/", res->location);
}
TEST(YahooRedirectTest3, NewResultInterface) {
TEST(YahooRedirectTest3, NewResultInterface_Online) {
Client cli("https://yahoo.com");
auto res = cli.Get("/");
@@ -4404,20 +4551,20 @@ TEST(YahooRedirectTest3, NewResultInterface) {
}
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
TEST(DecodeWithChunkedEncoding, BrotliEncoding_Online) {
Client cli("https://cdnjs.cloudflare.com");
auto res =
cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "br"}});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(287630, res->body.size());
EXPECT_EQ(287630U, res->body.size());
EXPECT_EQ("application/javascript; charset=utf-8",
res->get_header_value("Content-Type"));
}
#endif
TEST(HttpsToHttpRedirectTest, SimpleInterface) {
TEST(HttpsToHttpRedirectTest, SimpleInterface_Online) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
auto res =
@@ -4428,7 +4575,7 @@ TEST(HttpsToHttpRedirectTest, SimpleInterface) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
TEST(HttpsToHttpRedirectTest2, SimpleInterface_Online) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
@@ -4441,7 +4588,7 @@ TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
TEST(HttpsToHttpRedirectTest3, SimpleInterface_Online) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
@@ -4452,4 +4599,37 @@ TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpToHttpsRedirectTest, CertFile) {
Server svr;
ASSERT_TRUE(svr.is_valid());
svr.Get("/index", [&](const Request &, Response &res) {
res.set_redirect("https://127.0.0.1:1235/index");
svr.stop();
});
SSLServer ssl_svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(ssl_svr.is_valid());
ssl_svr.Get("/index", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
ssl_svr.stop();
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
thread t2 = thread([&]() { ASSERT_TRUE(ssl_svr.listen("127.0.0.1", 1235)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Client cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
cli.enable_server_certificate_verification(true);
cli.set_follow_location(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/index");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
t2.join();
}
#endif
+7
View File
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
configure_file(input: 'index.html', output: 'index.html', copy: true)
configure_file(input: 'test.abcde', output: 'test.abcde', copy: true)
configure_file(input: 'test.html', output: 'test.html', copy: true)
+6
View File
@@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
configure_file(input: 'index.html', output: 'index.html', copy: true)
configure_file(input: 'test.html', output: 'test.html', copy: true)
+6
View File
@@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
configure_file(input: 'index.html', output: 'index.html', copy: true)
configure_file(input: 'test.html', output: 'test.html', copy: true)