Compare commits

...

80 Commits

Author SHA1 Message Date
yhirose 615867322d Fixed build errors and apply clangformat 2020-11-29 12:29:18 -05:00
David Wu 02d3cd5909 Fix multiple threading bugs including #699 and #697 2020-11-29 12:29:15 -05:00
yhirose 47e5af15ea Updated README 2020-11-22 09:22:40 -05:00
yhirose a5c239c174 Fix #765 2020-11-21 16:35:31 -05:00
yhirose c2afc5ca44 Added chunked content provider support on client 2020-11-21 08:46:50 -05:00
yhirose cee062d4c9 Fixed unit tests due to the change in #763 2020-11-19 21:04:46 -05:00
Unkorunk b21dc8cbe0 Fix incorrect content_encoding for Brotli (#763) 2020-11-19 12:39:20 -05:00
372046933 e1133a2dcb std::tolower is undefined if the argument's value is neither representable as unsigned char nor equal to EOF (#761)
Co-authored-by: taoxu <taoxu@bilibili.com>
2020-11-19 09:21:40 -05:00
yhirose e273fec93c Fixed Visual Studio setup 2020-11-19 09:17:59 -05:00
yhirose 95d0b073bd Fix #754 2020-11-15 08:13:08 -05:00
vawen 9c7d841b37 Fix: #746 Compile error under VS2015 (#747)
Co-authored-by: jigarcia@vaxtor.es <jigarcia@vaxtor.es>
2020-11-10 08:41:53 -05:00
yhirose f086bf5310 Fix #738 2020-11-08 18:16:23 -05:00
yhirose 6613d7b7ad Fixed warnings 2020-11-08 17:59:11 -05:00
yhirose 6adf130bf3 Fix #739 2020-11-07 21:54:47 -05:00
miketsts b6b2eaf5bc Add unit test SSLConnectTimeout (#741)
Add unit test for issue #682 fixed in PR #728, which does not contain
the test of its own.

The test creates a fake SSL server, inherited from SSLServer, which
does not create an SSL context. When an SSL client attempts to send it
a request, it gets a timeout error. Prior to PR #728, the client would
wait indefinitely

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-07 09:41:20 -05:00
yhirose eb4b7c70a9 Fix #737 2020-11-07 09:33:22 -05:00
yhirose 84661ea6ed Refactoring 2020-11-05 07:06:53 -05:00
yhirose 041122908c Fix problem with invalid range 2020-11-05 00:20:59 -05:00
yhirose 401de608df Fixed debug option problem. 2020-11-05 00:20:59 -05:00
yhirose 726c64cf10 Code format 2020-11-05 00:20:59 -05:00
Omkar Jadhav e1f781a21a [oss-fuzz] Enable msan for fuzz tests (#734)
* Disable openssl as we're not using it in fuzz tests

* Increase timeout in ClientStop test
2020-11-04 11:30:56 -05:00
Omkar Jadhav 72b81badad Fix issues reported by oss-fuzz (#729)
* Fix oss-fuzz issue #26529

* Add test for oss-fuzz issue  #26598

* Fix oss-fuzz issue  #26632

* Revert change and add new test cases
2020-11-03 11:36:02 -05:00
yhirose 17428a8fbf Fixed warning 2020-11-03 09:16:28 -05:00
Daniel Ottiger 6e1879dfae ssl-verify-host: fix verifying ip addresses containing zero's (#732)
* ssl-verify-host: fix verifying ip addresses containing zero's

If the subject alternate name contained an ip address with an zero
(like 10.42.0.1) it could not successfully verify.
It is because in c++ strings are null-terminated
and therefore strlen(name) would return a wrong result.
As I can not see why we can not trust the length returned by openssl,
lets drop this check.

* ssl-verify-host: add test case

lets try to validate against 127.0.0.1

Co-authored-by: Daniel Ottiger <daniel.ottiger@ch.schindler.com>
2020-11-02 20:27:34 -05:00
miketsts eb1d2e04bc SSL_connect and SSL_accept in non-blocking mode (#728)
SSL connection is performed in two steps:
First, a regular socket connection is established.
Then, SSL_connect/SSL_accept is called to establish SSL handshake.

If a network problem occurs during the second stage, SSL_connect on
the client may hang indefinitely.

The non-blocking mode solves this problem.

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-02 17:05:08 -05:00
yhirose c909ffa758 Fix #731 2020-11-01 21:03:47 -05:00
yhirose ff5677ad19 Updated README 2020-10-27 20:35:56 -04:00
yhirose 8b1b31ac20 Fix #723 2020-10-27 20:32:19 -04:00
yhirose 953600c177 Fixed compiler error for old compiler. 2020-10-27 12:23:37 -04:00
yhirose 536e7eb7f2 Revert "Fix #697". (It broke unit test...)
This reverts commit 6d66721ba1.
2020-10-25 20:22:39 -04:00
yhirose 6d66721ba1 Fix #697 2020-10-25 16:55:54 -04:00
yhirose 3b29cd0bdc Fix #698 2020-10-25 12:14:54 -04:00
yhirose 109b624dfe Fix #708 (#713)
* Fix #708

* Rename ContentReceiver2 to ContentReceiverWithProgress
2020-10-22 11:48:43 -04:00
yhirose 0ed70c4d9f Fixed unit test errors 2020-10-21 14:34:27 -04:00
yhirose a50b7591ca Fix #714 2020-10-21 13:02:33 -04:00
yhirose bf8fc11b53 Code cleanup 2020-10-20 21:24:47 -04:00
Omkar Jadhav bc4a613b6d Fix suffix-byte-range issue (#711) 2020-10-20 11:11:27 -04:00
yhirose 4bb001351c Fix #705 2020-10-19 22:13:24 -04:00
yhirose e155ba44bb Fix #706 2020-10-19 15:23:35 -04:00
Muchamad Arifin Dwi P a4a9637738 Fix #700 null pointer exception (#702) 2020-10-16 20:44:14 -04:00
Omkar Jadhav 5292142046 Add cpp-httplib to oss-fuzz (#684)
* *Add server fuzzer target  and seed corpus
* Add fuzz_test option to Makefile

* Fix #685

* Try to fix Github actions on Ubuntu

* Added ReadTimeoutSSL test

* Comment out `-fsanitize=address`

* Rebase upstream changes

* remove address sanitizer temporarily

* Add separate Makefile for fuzzing

* 1. Remove special char from dictionary
2. Clean fuzzing/Makefile

* Use specific path to avoid accidently linking openssl version brought in by oss-fuzz

* remove addition of flags

* Refactor Makefile

* Add missing newline

* Add fuzztest to github workflow

* Fix

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2020-10-15 08:11:40 -04:00
Snape3058 cc5147ad72 Replace shared_ptr with unique_ptr for better performance (#695)
* Backport std::make_unique from C++14.

* Replace shared_ptr with unique_ptr for better performance.

Co-authored-by: Ella <maxutong16@otcaix.iscas.ac.cn>
2020-10-15 08:09:11 -04:00
Andrew Gasparovic fffbf1a669 Use move semantics instead of copy for functions (#692)
* Use move semantics instead of copy for functions

In some cases, a few more copies could be prevented by changing function definitions to accept parameters by const-ref, rather than by value, but I didn't want to change public signatures.

* Fix two use-after-move errors
2020-10-11 19:00:36 -04:00
lightvector d37bc0fb4d Allow client to specify boundary and use more entropy by default (#691) (#694) 2020-10-11 15:34:54 -04:00
Andrew Gasparovic 6d60dc8839 Add cache_control parameter to set_mount_point (#688)
* Add `cache_control` parameter to `set_mount_point`

Specifies the Cache-Control header value to return when specified. For example:

```
svr.set_mount_point("/assets", "public/assets", "public, max-age=604800, immutable");
```

* Add default for cache_control

Default to "no-cache", which is implicitly what is happening today.

* Change set_mount_point to accept Headers

* Don't use C++17 destructuring
2020-10-10 20:46:08 -04:00
Wang Gao b713a3a651 fix MSVC2015 error: std::tolower to ::lower (#689) 2020-10-10 12:02:50 -04:00
yhirose 7e8db1dc68 Comment out -fsanitize=address 2020-10-08 23:14:53 -04:00
yhirose 316add860b Added ReadTimeoutSSL test 2020-10-08 22:55:09 -04:00
yhirose 79ce6f1745 Try to fix Github actions on Ubuntu 2020-10-08 21:49:47 -04:00
yhirose 09fdf4eacd Fix #685 2020-10-08 21:37:42 -04:00
Omkar Jadhav 143b2dd15a Fix memory leak due caused due to X509_STORE (#671)
* Fix memory leak due caused due to X509_STORE

* Add test for repro and address sanitizer to compiler flags

* Add comment

* Sync

* Associate ca_store with ssl context within set_ca_cert_store()

* Split SlowPost test

* Fix #674

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2020-10-02 13:17:37 -04:00
yhirose e2c4e9d95c Fix #674 2020-09-29 19:22:28 -04:00
yhirose d87082f04b Split SlowPost test 2020-09-29 19:17:34 -04:00
yhirose cc14855ba0 Fix #661 2020-09-26 04:50:09 -04:00
yhirose 56c418745f Fixed conction close problem with HTTP 1.0 client 2020-09-25 20:58:49 -04:00
yhirose 4ce9911837 Add <sstream> 2020-09-25 18:17:32 -04:00
yhirose 559c407552 Adjusted SlowRequest test 2020-09-25 18:13:10 -04:00
yhirose a2f4e29a7b Add set_keep_alive_timeout 2020-09-25 17:57:33 -04:00
mi01 b8cf739d27 Add cctype header (#656) 2020-09-16 16:32:49 -04:00
yhirose aec2f9521d Updated documentation 2020-09-15 10:11:46 -04:00
yhirose 7b55ecdc59 Fixed #650 2020-09-12 16:11:14 -04:00
tmahring e9575bcb78 don't replace plus with space in headers (#649)
* don't replace plus with space in headers

* fixed forward handling with changed header parsing

* add test for boundaries containing plus chars
2020-09-10 20:27:01 -04:00
Jonas Minnberg 308aeb187b Undefined if2ip() also on Android since getifaddrs() does not exist. (#648)
Co-authored-by: Jonas Minnberg <jonas@minnberg.se>
2020-09-10 07:52:01 -04:00
Jodi the Tigger 05d18f2bc5 Fix -D build flags containing escaped quotes (#645)
Fixes #638

Done by removed unneeded quotes from cmake's add_compiler_definitions()
2020-09-08 21:35:50 -04:00
Ivan Fefer 3da4a0ac69 Add compression buffer size customization (#644)
* add compression buffer size customization and small brotli refactor

* allocat brotli buffer once

* add init to brotli decoder buffer
2020-09-08 12:18:14 -04:00
yhirose 9d12b3f20e Fixed warnings and refactoring 2020-09-03 20:33:30 -04:00
Omkar Jadhav 852a374748 Fix server crash caused due to regex complexity while matching headers. (#632)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.

* Fix server crash caused due to regex complexity while matching headers.

While parsing content-type header in multipart form request the server crashes due to the exhaustion of max iterations performed while matching the input string with content-type regex.
Have removed the regex which might use backtracking while matching and replaced it with manual string processing. Have added tests as well.

* Remove magic number

Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>

Co-authored-by: yhirose <yhirose@users.noreply.github.com>
Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>
2020-09-03 13:17:52 -04:00
Ivan Fefer 3b5bab3308 Fix gzip_decompressor in case of one chunk being exactly equal to buffer size (#636)
* add larget chunks test

* revert test

* Fix gzip decoder in case of chunk being equal to buffer size

* add test
2020-09-03 12:20:02 -04:00
yhirose 69e75f4a67 Fix #635. HTTPS request stucked with proxy (#637) 2020-09-03 12:17:53 -04:00
Omkar Jadhav b0fd4befb1 Fix query parsing issues (#629)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.
2020-08-28 09:43:28 -04:00
yhirose 3e80666a74 Fix #628 2020-08-27 19:45:28 -04:00
yhirose 3e4567bae8 Updated Repl.it examples 2020-08-27 14:16:35 -04:00
yhirose db075d8cf9 Added Repl.it examples 2020-08-27 14:06:57 -04:00
yhirose 16df0ef37e Code cleanup 2020-08-26 12:18:49 -04:00
Ivan Fefer f1a2ac5108 Avoid copying of content provider if possible (#627) 2020-08-26 08:56:51 -04:00
yhirose e5743b358d Updated README 2020-08-25 20:26:53 -04:00
Ivan Fefer 1184bbe4cb Fix typo in README (#625) 2020-08-25 06:38:16 -04:00
Felix Kästner 9dcffda7ae Fix cmake execute_process working directory (#622)
Correct the working directory of the execute_process call, to get the version from git tags.
The working directory should be the the directory of this libary. Otherwise, if you include httplib
as a git submodule and call add_subdirectory, the execute_process command will be run in a wrong
directory leading to error.
2020-08-24 17:23:01 -04:00
yhirose e5903635e2 Fix #619 2020-08-22 12:54:43 -04:00
yhirose 510b4eaaae Fix #613 2020-08-17 13:40:06 -04:00
14 changed files with 2239 additions and 765 deletions
+5 -2
View File
@@ -20,16 +20,19 @@ jobs:
uses: actions/checkout@v2
- name: install brotli library on ubuntu
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get install -y libbrotli-dev
run: sudo apt update && sudo apt-get install -y libbrotli-dev
- name: install brotli library on macOS
if: matrix.os == 'macOS-latest'
run: brew install brotli
- name: make
if: matrix.os != 'windows-latest'
run: cd test && make
- name: check fuzz test target
if: matrix.os == 'ubuntu-latest'
run: cd test && make -f Makefile.fuzz_test
- name: setup msbuild on windows
if: matrix.os == 'windows-latest'
uses: warrenbuckley/Setup-MSBuild@v1
uses: microsoft/setup-msbuild@v1.0.2
- name: make-windows
if: matrix.os == 'windows-latest'
run: |
+4 -3
View File
@@ -66,6 +66,7 @@ if(Git_FOUND)
# Gets the latest tag as a string like "v0.6.6"
# Can silently fail if git isn't on the system
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE _raw_version_string
ERROR_VARIABLE _git_tag_error
)
@@ -235,9 +236,9 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
# Set the definitions to enable optional features
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:"CPPHTTPLIB_BROTLI_SUPPORT">
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:"CPPHTTPLIB_ZLIB_SUPPORT">
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:"CPPHTTPLIB_OPENSSL_SUPPORT">
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
)
# Cmake's find_package search path is different based on the system
+90 -10
View File
@@ -7,8 +7,41 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include **httplib.h** file in your code!
Server Example
--------------
NOTE: This is a 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
Simple examples
---------------
#### Server
```c++
httplib::Server svr;
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("0.0.0.0", 8080);
```
#### Client
```c++
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
auto res = cli.Get("/hi");
res->status; // 200
res->body; // "Hello World!"
```
### Try out the examples on Repl.it!
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
Server
------
```c++
#include <httplib.h>
@@ -197,7 +230,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
// prepare data...
sink.write(data.data(), data.size());
} else {
done(); // No more data
sink.done(); // No more data
}
return true; // return 'false' if you want to cancel the process.
});
@@ -264,13 +297,18 @@ Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/e
### Default thread pool support
`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`.
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
If you want to set the thread count at runtime, there is no convenient way... But here is how.
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
```cpp
svr.new_task_queue = [] { return new ThreadPool(12); };
```
### Override the default thread pool with yours
You can supply your own thread pool implementation according to your need.
```cpp
class YourThreadPoolTaskQueue : public TaskQueue {
public:
@@ -295,8 +333,8 @@ svr.new_task_queue = [] {
};
```
Client Example
--------------
Client
------
```c++
#include <httplib.h>
@@ -327,6 +365,27 @@ httplib::Client cli("http://localhost:8080");
httplib::Client cli("https://localhost");
```
### Error code
Here is the list of errors from `Result::error()`.
```c++
enum Error {
Success = 0,
Unknown,
Connection,
BindIPAddress,
Read,
Write,
ExceedRedirectCount,
Canceled,
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
UnsupportedMultipartBoundaryChars
};
```
### GET with HTTP headers
```c++
@@ -443,7 +502,7 @@ auto res = cli.Get(
```cpp
std::string body = ...;
auto res = cli_.Post(
auto res = cli.Post(
"/stream", body.size(),
[](size_t offset, size_t length, DataSink &sink) {
sink.write(body.data() + offset, length);
@@ -452,6 +511,21 @@ auto res = cli_.Post(
"text/plain");
```
### Chunked transfer encoding
```cpp
auto res = cli.Post(
"/stream",
[](size_t offset, DataSink &sink) {
sink.os << "chunked data 1";
sink.os << "chunked data 2";
sink.os << "chunked data 3";
sink.done();
return true; // return 'false' if you want to cancel the request.
},
"text/plain");
```
### With Progress Callback
```cpp
@@ -564,13 +638,19 @@ NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
SSLServer svr("./cert.pem", "./key.pem");
httplib::SSLServer svr("./cert.pem", "./key.pem");
SSLClient cli("localhost", 8080);
httplib::SSLClient cli("localhost", 1234); // or `httplib::Client cli("https://localhost:1234");`
cli.set_ca_cert_path("./ca-bundle.crt");
cli.enable_server_certificate_verification(true);
```
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE
can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its
internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might
be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Compression
-----------
+1293 -714
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,5 @@
#CXX = clang++
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
CXXFLAGS = -g -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -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
@@ -25,6 +24,7 @@ test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
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
+36
View File
@@ -0,0 +1,36 @@
#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
+27
View File
@@ -0,0 +1,27 @@
#CXX = clang++
# Do not add default sanitizer flags here as OSS-fuzz adds its own sanitizer flags.
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
# Using full path to libssl and libcrypto to avoid accidentally picking openssl libs brought in by msan.
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -I$(OPENSSL_DIR)/lib /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a
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
# Runs all the tests and also fuzz tests against seed corpus.
all : server_fuzzer
./server_fuzzer corpus/*
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : server_fuzzer.cc ../../httplib.h
# $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
zip -q -r server_fuzzer_seed_corpus.zip corpus
clean:
rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip
+1
View File
@@ -0,0 +1 @@
PUT /search/sample?a=12 HTTP/1.1
+5
View File
@@ -0,0 +1,5 @@
GET /hello.htm HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
+90
View File
@@ -0,0 +1,90 @@
#include <memory>
#include <httplib.h>
class FuzzedStream : public httplib::Stream {
public:
FuzzedStream(const uint8_t* data, size_t size)
: data_(data), size_(size), read_pos_(0) {}
ssize_t read(char* ptr, size_t size) override {
if (size + read_pos_ > size_) {
size = size_ - read_pos_;
}
memcpy(ptr, data_ + read_pos_, size);
read_pos_ += size;
return size;
}
ssize_t write(const char* ptr, size_t size) override {
response_.append(ptr, size);
return static_cast<int>(size);
}
int write(const char* ptr) { return write(ptr, strlen(ptr)); }
int write(const std::string& s) { return write(s.data(), s.size()); }
std::string get_remote_addr() const { return ""; }
bool is_readable() const override { return true; }
bool is_writable() const override { return true; }
void get_remote_ip_and_port(std::string &ip, int &port) const override {
ip = "127.0.0.1";
port = 8080;
}
socket_t socket() const override { return 0; }
private:
const uint8_t* data_;
size_t size_;
size_t read_pos_;
std::string response_;
};
class FuzzableServer : public httplib::Server {
public:
void ProcessFuzzedRequest(FuzzedStream& stream) {
bool connection_close = false;
process_request(stream, /*last_connection=*/false, connection_close,
nullptr);
}
};
static FuzzableServer g_server;
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
g_server.Get(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
g_server.Post(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
g_server.Put(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
g_server.Patch(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
g_server.Delete(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
g_server.Options(R"(.*)",
[&](const httplib::Request& req, httplib::Response& res) {
res.set_content("response content", "text/plain");
});
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedStream stream{data, size};
g_server.ProcessFuzzedRequest(stream);
return 0;
}
+224
View File
@@ -0,0 +1,224 @@
# Sources: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
# misc
"HTTP/1.1"
# verbs
"CONNECT"
"DELETE"
"GET"
"HEAD"
"OPTIONS"
"PATCH"
"POST"
"PUT"
"TRACE"
# Webdav/caldav verbs
"ACL"
"BASELINE-CONTROL"
"BIND"
"CHECKIN"
"CHECKOUT"
"COPY"
"LABEL"
"LINK"
"LOCK"
"MERGE"
"MKACTIVITY"
"MKCALENDAR"
"MKCOL"
"MKREDIRECTREF"
"MKWORKSPACE"
"MOVE"
"ORDERPATCH"
"PRI"
"PROPFIND"
"PROPPATCH"
"REBIND"
"REPORT"
"SEARCH"
"UNBIND"
"UNCHECKOUT"
"UNLINK"
"UNLOCK"
"UPDATE"
"UPDATEREDIRECTREF"
"VERSION-CONTROL"
# Fields
"A-IM"
"Accept"
"Accept-Charset"
"Accept-Datetime"
"Accept-Encoding"
"Accept-Language"
"Accept-Patch"
"Accept-Ranges"
"Access-Control-Allow-Credentials"
"Access-Control-Allow-Headers"
"Access-Control-Allow-Methods"
"Access-Control-Allow-Origin"
"Access-Control-Expose-Headers"
"Access-Control-Max-Age"
"Access-Control-Request-Headers"
"Access-Control-Request-Method"
"Age"
"Allow"
"Alt-Svc"
"Authorization"
"Cache-Control"
"Connection"
"Connection:"
"Content-Disposition"
"Content-Encoding"
"Content-Language"
"Content-Length"
"Content-Location"
"Content-MD5"
"Content-Range"
"Content-Security-Policy"
"Content-Type"
"Cookie"
"DNT"
"Date"
"Delta-Base"
"ETag"
"Expect"
"Expires"
"Forwarded"
"From"
"Front-End-Https"
"HTTP2-Settings"
"Host"
"IM"
"If-Match"
"If-Modified-Since"
"If-None-Match"
"If-Range"
"If-Unmodified-Since"
"Last-Modified"
"Link"
"Location"
"Max-Forwards"
"Origin"
"P3P"
"Pragma"
"Proxy-Authenticate"
"Proxy-Authorization"
"Proxy-Connection"
"Public-Key-Pins"
"Range"
"Referer"
"Refresh"
"Retry-After"
"Save-Data"
"Server"
"Set-Cookie"
"Status"
"Strict-Transport-Security"
"TE"
"Timing-Allow-Origin"
"Tk"
"Trailer"
"Transfer-Encoding"
"Upgrade"
"Upgrade-Insecure-Requests"
"User-Agent"
"Vary"
"Via"
"WWW-Authenticate"
"Warning"
"X-ATT-DeviceId"
"X-Content-Duration"
"X-Content-Security-Policy"
"X-Content-Type-Options"
"X-Correlation-ID"
"X-Csrf-Token"
"X-Forwarded-For"
"X-Forwarded-Host"
"X-Forwarded-Proto"
"X-Frame-Options"
"X-Http-Method-Override"
"X-Powered-By"
"X-Request-ID"
"X-Requested-With"
"X-UA-Compatible"
"X-UIDH"
"X-Wap-Profile"
"X-WebKit-CSP"
"X-XSS-Protection"
# Source: string and character literals in httplib.h
" "
"&"
", "
"-"
"--"
"."
".."
":"
"="
" = = "
"0123456789abcdef"
"%02X"
"%0A"
"\\x0a\\x0d"
"%0D"
"%20"
"%27"
"%2B"
"%2C"
"%3A"
"%3B"
"application/javascript"
"application/json"
"application/pdf"
"application/xhtml+xml"
"application/xml"
"application/x-www-form-urlencoded"
"Bad Request"
"boundary="
"bytes="
"chunked"
"close"
"CONNECT"
"css"
"Forbidden"
"Found"
"gif"
"gzip"
"html"
"ico"
"image/gif"
"image/jpg"
"image/png"
"image/svg+xml"
"image/x-icon"
"index.html"
"Internal Server Error"
"jpeg"
"js"
"json"
"Location"
"Moved Permanently"
"multipart/form-data"
"Not Found"
"Not Modified"
"OK"
"pdf"
"png"
"Range"
"REMOTE_ADDR"
"See Other"
"svg"
"text/"
"text/css"
"text/html"
"text/plain"
"txt"
"Unsupported Media Type"
"xhtml"
"xml"
@@ -0,0 +1,35 @@
// Copyright 2017 Google Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// This runner does not do any fuzzing, but allows us to run the fuzz target
// on the test corpus or on a single file,
// e.g. the one that comes from a bug report.
#include <cassert>
#include <iostream>
#include <fstream>
#include <vector>
// Forward declare the "fuzz target" interface.
// We deliberately keep this inteface simple and header-free.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
// It reads all files passed as parameters and feeds their contents
// one by one into the fuzz target (LLVMFuzzerTestOneInput).
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();
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());
LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()),
bytes.size());
std::cout << "Execution successful" << std::endl;
}
std::cout << "Execution finished" << std::endl;
return 0;
}
+424 -34
View File
@@ -5,8 +5,10 @@
#include <chrono>
#include <future>
#include <thread>
#include <atomic>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_CERT2_FILE "./cert2.pem"
#define SERVER_PRIVATE_KEY_FILE "./key.pem"
#define CA_CERT_FILE "./ca-bundle.crt"
#define CLIENT_CA_CERT_FILE "./rootCA.cert.pem"
@@ -44,6 +46,12 @@ TEST(StartupTest, WSAStartup) {
}
#endif
TEST(TrimTests, TrimStringTests) {
EXPECT_EQ("abc", detail::trim_copy("abc"));
EXPECT_EQ("abc", detail::trim_copy(" abc "));
EXPECT_TRUE(detail::trim_copy("").empty());
}
TEST(SplitTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
Params dic;
@@ -66,6 +74,23 @@ TEST(SplitTest, ParseQueryString) {
EXPECT_EQ("val3", dic.find("key3")->second);
}
TEST(SplitTest, ParseInvalidQueryTests) {
{
string s = " ";
Params dict;
detail::parse_query_text(s, dict);
EXPECT_TRUE(dict.empty());
}
{
string s = " = =";
Params dict;
detail::parse_query_text(s, dict);
EXPECT_TRUE(dict.empty());
}
}
TEST(ParseQueryTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
Params dic;
@@ -111,6 +136,17 @@ TEST(GetHeaderValueTest, RegularValue) {
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, SetContent) {
Response res;
res.set_content("html", "text/html");
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("text/plain", res.get_header_value("Content-Type"));
}
TEST(GetHeaderValueTest, RegularValueInt) {
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
auto val =
@@ -743,6 +779,7 @@ TEST(YahooRedirectTest, Redirect) {
res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://yahoo.com/", res->location);
}
#if 0
@@ -806,7 +843,7 @@ TEST(UrlWithSpace, Redirect) {
}
#endif
TEST(Server, BindDualStack) {
TEST(BindServerTest, BindDualStack) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
@@ -839,7 +876,7 @@ TEST(Server, BindDualStack) {
ASSERT_FALSE(svr.is_running());
}
TEST(Server, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
@@ -848,7 +885,7 @@ TEST(Server, BindAndListenSeparately) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLServer, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparatelySSL) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
int port = svr.bind_to_any_port("0.0.0.0");
@@ -858,6 +895,41 @@ TEST(SSLServer, BindAndListenSeparately) {
}
#endif
TEST(ErrorHandlerTest, ContentLength) {
Server svr;
svr.set_error_handler([](const Request & /*req*/, Response &res) {
res.status = 200;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
res.status = 524;
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -1064,7 +1136,7 @@ protected:
})
.Post("/multipart",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(5u, req.files.size());
EXPECT_EQ(6u, req.files.size());
ASSERT_TRUE(!req.has_file("???"));
ASSERT_TRUE(req.body.empty());
@@ -1093,6 +1165,13 @@ protected:
EXPECT_EQ("application/octet-stream", file.content_type);
EXPECT_EQ(0u, file.content.size());
}
{
const auto &file = req.get_file_value("file4");
EXPECT_TRUE(file.filename.empty());
EXPECT_EQ(0u, file.content.size());
EXPECT_EQ("application/json tmp-string", file.content_type);
}
})
.Post("/empty",
[&](const Request &req, Response &res) {
@@ -1303,8 +1382,11 @@ protected:
virtual void TearDown() {
svr_.stop();
for (auto &t : request_threads_) {
t.join();
if (!request_threads_.empty()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
for (auto &t : request_threads_) {
t.join();
}
}
t_.join();
}
@@ -1354,6 +1436,7 @@ TEST_F(ServerTest, GetMethod302Redirect) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ("/hi", res->location);
}
TEST_F(ServerTest, GetMethod404) {
@@ -1582,6 +1665,7 @@ TEST_F(ServerTest, PostMethod303Redirect) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("redirected.", res->body);
EXPECT_EQ("/2", res->location);
}
TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
@@ -1785,7 +1869,7 @@ TEST_F(ServerTest, MultipartFormData) {
{"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
{"file2", "{\n \"world\", true\n}\n", "world.json", "application/json"},
{"file3", "", "", "application/octet-stream"},
};
{"file4", "", "", " application/json tmp-string "}};
auto res = cli_.Post("/multipart", items);
@@ -1864,6 +1948,41 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
EXPECT_EQ(std::string("bcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-3"}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("3", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("efg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-9999"}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeError) {
auto res = cli_.Get("/streamed-with-range",
{{"Range", "bytes=92233720368547758079223372036854775806-"
"92233720368547758079223372036854775807"}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res =
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
EXPECT_EQ(206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
auto res =
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
@@ -1899,8 +2018,7 @@ TEST_F(ServerTest, ClientStop) {
}));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::seconds(2));
while (cli_.is_socket_open()) {
cli_.stop();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
@@ -1946,6 +2064,12 @@ TEST_F(ServerTest, GetWithRange4) {
EXPECT_EQ(std::string("fg"), res->body);
}
TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) {
auto res = cli_.Get("/with-range", {{make_range_header({{10000, 20000}})}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetWithRangeMultipart) {
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
@@ -1955,6 +2079,13 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
EXPECT_EQ(269, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
auto res =
cli_.Get("/with-range", {{make_range_header({{-1, 2}, {10000, 30000}})}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res);
@@ -2021,7 +2152,6 @@ TEST_F(ServerTest, SlowRequest) {
std::thread([=]() { auto res = cli_.Get("/slow"); }));
request_threads_.push_back(
std::thread([=]() { auto res = cli_.Get("/slow"); }));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
TEST_F(ServerTest, SlowPost) {
@@ -2038,9 +2168,14 @@ TEST_F(ServerTest, SlowPost) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, SlowPostFail) {
char buffer[64 * 1024];
memset(buffer, 0x42, sizeof(buffer));
cli_.set_write_timeout(0, 0);
res = cli_.Post(
auto res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
@@ -2086,6 +2221,31 @@ TEST_F(ServerTest, PostWithContentProviderAbort) {
EXPECT_EQ(Error::Canceled, res.error());
}
TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
},
"text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PostWithContentProviderWithoutLengthAbort) {
auto res = cli_.Post(
"/post", [](size_t /*offset*/, DataSink & /*sink*/) { return false; },
"text/plain");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
cli_.set_compress(true);
@@ -2116,6 +2276,33 @@ TEST_F(ServerTest, PostWithContentProviderWithGzipAbort) {
EXPECT_EQ(Error::Canceled, res.error());
}
TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
},
"text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PostWithContentProviderWithoutLengthWithGzipAbort) {
cli_.set_compress(true);
auto res = cli_.Post(
"/post", [](size_t /*offset*/, DataSink & /*sink*/) { return false; },
"text/plain");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST_F(ServerTest, PutLargeFileWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
@@ -2157,12 +2344,53 @@ TEST_F(ServerTest, GetStreamedChunkedWithGzip2) {
EXPECT_EQ(200, res->status);
EXPECT_EQ(std::string("123456789"), res->body);
}
TEST(GzipDecompressor, ChunkedDecompression) {
std::string data;
for (size_t i = 0; i < 32 * 1024; ++i) {
data.push_back(static_cast<char>('a' + i % 26));
}
std::string compressed_data;
{
httplib::detail::gzip_compressor compressor;
bool result = compressor.compress(
data.data(), data.size(),
/*last=*/true, [&](const char *data, size_t size) {
compressed_data.insert(compressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
}
std::string decompressed_data;
{
httplib::detail::gzip_decompressor decompressor;
// Chunk size is chosen specificaly to have a decompressed chunk size equal
// to 16384 bytes 16384 bytes is the size of decompressor output buffer
size_t chunk_size = 130;
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size();
chunk_begin += chunk_size) {
size_t current_chunk_size =
std::min(compressed_data.size() - chunk_begin, chunk_size);
bool result = decompressor.decompress(
compressed_data.data() + chunk_begin, current_chunk_size,
[&](const char *data, size_t size) {
decompressed_data.insert(decompressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
}
}
ASSERT_EQ(data, decompressed_data);
}
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
TEST_F(ServerTest, GetStreamedChunkedWithBrotli) {
Headers headers;
headers.emplace("Accept-Encoding", "brotli");
headers.emplace("Accept-Encoding", "br");
auto res = cli_.Get("/streamed-chunked", headers);
ASSERT_TRUE(res);
@@ -2172,7 +2400,7 @@ TEST_F(ServerTest, GetStreamedChunkedWithBrotli) {
TEST_F(ServerTest, GetStreamedChunkedWithBrotli2) {
Headers headers;
headers.emplace("Accept-Encoding", "brotli");
headers.emplace("Accept-Encoding", "br");
auto res = cli_.Get("/streamed-chunked2", headers);
ASSERT_TRUE(res);
@@ -2252,6 +2480,41 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, PostMulitpartPlusBoundary) {
MultipartFormDataItems items = {
{"text1", "text default", "", ""},
{"text2", "aωb", "", ""},
{"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
{"file2", "{\n \"world\", true\n}\n", "world.json", "application/json"},
{"file3", "", "", "application/octet-stream"},
};
auto boundary = std::string("+++++");
std::string body;
for (const auto &item : items) {
body += "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
if (!item.filename.empty()) {
body += "; filename=\"" + item.filename + "\"";
}
body += "\r\n";
if (!item.content_type.empty()) {
body += "Content-Type: " + item.content_type + "\r\n";
}
body += "\r\n";
body += item.content + "\r\n";
}
body += "--" + boundary + "--\r\n";
std::string content_type = "multipart/form-data; boundary=" + boundary;
auto res = cli_.Post("/content_receiver", body, content_type.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, PostContentReceiverGzip) {
cli_.set_compress(true);
auto res = cli_.Post("/content_receiver", "content", "text/plain");
@@ -2486,7 +2749,7 @@ TEST_F(ServerTest, Brotli) {
auto res = cli_.Get("/compress", headers);
ASSERT_TRUE(res);
EXPECT_EQ("brotli", res->get_header_value("Content-Encoding"));
EXPECT_EQ("br", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("19", res->get_header_value("Content-Length"));
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
@@ -2499,7 +2762,7 @@ TEST_F(ServerTest, Brotli) {
// Sends a raw request to a server listening at HOST:PORT.
static bool send_request(time_t read_timeout_sec, const std::string &req,
std::string *resp = nullptr) {
Error error = Error::Success;
std::atomic<Error> error(Error::Success);
auto client_sock =
detail::create_client_socket(HOST, PORT, false, nullptr,
@@ -2733,15 +2996,14 @@ TEST(StreamingTest, NoContentLengthStreaming) {
Server svr;
svr.Get("/stream", [](const Request & /*req*/, Response &res) {
res.set_content_provider(
"text/plain", [](size_t offset, DataSink &sink) {
if (offset < 6) {
sink.os << (offset < 3 ? "a" : "b");
} else {
sink.done();
}
return true;
});
res.set_content_provider("text/plain", [](size_t offset, DataSink &sink) {
if (offset < 6) {
sink.os << (offset < 3 ? "a" : "b");
} else {
sink.done();
}
return true;
});
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
@@ -2879,6 +3141,48 @@ TEST(KeepAliveTest, ReadTimeout) {
ASSERT_FALSE(svr.is_running());
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, ReadTimeoutSSL) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("a", "text/plain");
});
svr.Get("/b", [&](const Request & /*req*/, Response &res) {
res.set_content("b", "text/plain");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.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));
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
cli.set_keep_alive(true);
cli.set_read_timeout(1);
auto resa = cli.Get("/a");
ASSERT_TRUE(!resa);
EXPECT_EQ(Error::Read, resa.error());
auto resb = cli.Get("/b");
ASSERT_TRUE(resb);
EXPECT_EQ(200, resb->status);
EXPECT_EQ("b", resb->body);
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
class ServerTestWithAI_PASSIVE : public ::testing::Test {
protected:
ServerTestWithAI_PASSIVE()
@@ -3013,6 +3317,19 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLClientTest, UpdateCAStore) {
httplib::SSLClient httplib_client("www.google.com");
auto ca_store_1 = X509_STORE_new();
X509_STORE_load_locations(ca_store_1, "/etc/ssl/certs/ca-certificates.crt",
nullptr);
httplib_client.set_ca_cert_store(ca_store_1);
auto ca_store_2 = X509_STORE_new();
X509_STORE_load_locations(ca_store_2, "/etc/ssl/certs/ca-certificates.crt",
nullptr);
httplib_client.set_ca_cert_store(ca_store_2);
}
TEST(SSLClientTest, ServerNameIndication) {
SSLClient cli("httpbin.org", 443);
auto res = cli.Get("/get");
@@ -3044,6 +3361,31 @@ TEST(SSLClientTest, ServerCertificateVerification3) {
ASSERT_EQ(301, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification4) {
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
SSLClient cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
cli.enable_server_certificate_verification(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/test");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
}
TEST(SSLClientTest, WildcardHostNameMatch) {
SSLClient cli("www.youtube.com");
@@ -3098,6 +3440,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
t.join();
}
#if !defined(_WIN32) || defined(OPENSSL_USE_APPLINK)
TEST(SSLClientServerTest, MemoryClientCertPresent) {
X509 *server_cert;
EVP_PKEY *server_private_key;
@@ -3173,6 +3516,7 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
t.join();
}
#endif
TEST(SSLClientServerTest, ClientCertMissing) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
@@ -3217,6 +3561,51 @@ TEST(SSLClientServerTest, TrustDirOptional) {
t.join();
}
TEST(SSLClientServerTest, SSLConnectTimeout) {
class NoListenSSLServer : public SSLServer {
public:
NoListenSSLServer(const char *cert_path, const char *private_key_path,
const char *client_ca_cert_file_path,
const char *client_ca_cert_dir_path = nullptr)
: SSLServer(cert_path, private_key_path, client_ca_cert_file_path,
client_ca_cert_dir_path),
stop_(false) {}
bool stop_;
private:
bool process_and_close_socket(socket_t /*sock*/) override {
// Don't create SSL context
while (!stop_) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return true;
}
};
NoListenSSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
CLIENT_CA_CERT_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
});
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(1);
auto res = cli.Get("/test");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLConnection, res.error());
svr.stop_ = true;
svr.stop();
t.join();
}
#endif
#ifdef _WIN32
@@ -3226,17 +3615,15 @@ TEST(CleanupTest, WSACleanup) {
}
#endif
// #ifndef CPPHTTPLIB_OPENSSL_SUPPORT
// TEST(NoSSLSupport, SimpleInterface) {
// Client cli("https://yahoo.com");
// ASSERT_FALSE(cli.is_valid());
// }
// #endif
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(NoSSLSupport, SimpleInterface) {
ASSERT_ANY_THROW(Client cli("https://yahoo.com"));
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(InvalidScheme, SimpleInterface) {
Client cli("scheme://yahoo.com");
ASSERT_FALSE(cli.is_valid());
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
}
TEST(NoScheme, SimpleInterface) {
@@ -3255,6 +3642,7 @@ TEST(YahooRedirectTest2, SimpleInterface) {
res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://yahoo.com/", res->location);
}
TEST(YahooRedirectTest3, SimpleInterface) {
@@ -3268,6 +3656,7 @@ TEST(YahooRedirectTest3, SimpleInterface) {
res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://www.yahoo.com/", res->location);
}
TEST(YahooRedirectTest3, NewResultInterface) {
@@ -3291,13 +3680,14 @@ TEST(YahooRedirectTest3, NewResultInterface) {
EXPECT_EQ(200, res.value().status);
EXPECT_EQ(200, (*res).status);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://www.yahoo.com/", res->location);
}
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
Client cli("https://cdnjs.cloudflare.com");
auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js",
{{"Accept-Encoding", "brotli"}});
auto res =
cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "br"}});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
+3
View File
@@ -16,3 +16,6 @@ emailAddress = test@email.address
[req_attributes]
challengePassword = 1234
[SAN]
subjectAltName=IP:127.0.0.1