mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85b4abbf16 | |||
| e42a358da8 | |||
| f008fe4539 | |||
| ddf41d29ef | |||
| 3f88a46c4a | |||
| 242706ea34 | |||
| a9f5f8683f | |||
| 60c2213893 | |||
| eb2d28bca2 | |||
| 86f637a246 | |||
| 2c07ec4600 | |||
| 871d8d67b0 | |||
| 7299713195 | |||
| 96afa7e108 | |||
| 55f57af0b9 | |||
| 6b35cd0116 | |||
| 99f2229e48 | |||
| b9641048fc | |||
| e9c6c6e609 | |||
| 40db42108f | |||
| 24bb1387d6 | |||
| d0bd4afb0b | |||
| 78ea786abd | |||
| 9cac2c9ceb | |||
| 0cff3245df | |||
| 0e3925db3f | |||
| c9a13d214b | |||
| 0954af2d4c | |||
| 7c1c952f5a | |||
| a6edfc730a | |||
| c1264bfedc | |||
| 90a5b6ceb0 | |||
| eb240ad2e5 | |||
| 88c961f37e | |||
| b952376968 | |||
| 5dd605d3a2 | |||
| 9c0c98b1ed | |||
| 615867322d | |||
| 02d3cd5909 | |||
| 47e5af15ea | |||
| a5c239c174 | |||
| c2afc5ca44 | |||
| cee062d4c9 | |||
| b21dc8cbe0 | |||
| e1133a2dcb | |||
| e273fec93c | |||
| 95d0b073bd | |||
| 9c7d841b37 | |||
| f086bf5310 | |||
| 6613d7b7ad | |||
| 6adf130bf3 | |||
| b6b2eaf5bc | |||
| eb4b7c70a9 | |||
| 84661ea6ed | |||
| 041122908c | |||
| 401de608df | |||
| 726c64cf10 | |||
| e1f781a21a | |||
| 72b81badad | |||
| 17428a8fbf | |||
| 6e1879dfae | |||
| eb1d2e04bc | |||
| c909ffa758 | |||
| ff5677ad19 | |||
| 8b1b31ac20 | |||
| 953600c177 | |||
| 536e7eb7f2 | |||
| 6d66721ba1 | |||
| 3b29cd0bdc | |||
| 109b624dfe | |||
| 0ed70c4d9f | |||
| a50b7591ca | |||
| bf8fc11b53 | |||
| bc4a613b6d | |||
| 4bb001351c | |||
| e155ba44bb | |||
| a4a9637738 | |||
| 5292142046 | |||
| cc5147ad72 | |||
| fffbf1a669 | |||
| d37bc0fb4d | |||
| 6d60dc8839 | |||
| b713a3a651 | |||
| 7e8db1dc68 | |||
| 316add860b | |||
| 79ce6f1745 | |||
| 09fdf4eacd | |||
| 143b2dd15a | |||
| e2c4e9d95c | |||
| d87082f04b |
@@ -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: |
|
||||
|
||||
@@ -5,7 +5,7 @@ cpp-httplib
|
||||
|
||||
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!
|
||||
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
||||
|
||||
NOTE: This is a 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
||||
|
||||
@@ -15,8 +15,15 @@ Simple examples
|
||||
#### Server
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// HTTP
|
||||
httplib::Server svr;
|
||||
|
||||
// HTTPS
|
||||
httplib::SSLServer svr;
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
@@ -27,12 +34,18 @@ svr.listen("0.0.0.0", 8080);
|
||||
#### Client
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// HTTP
|
||||
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
// HTTPS
|
||||
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
|
||||
|
||||
res->status; // 200
|
||||
res->body; // "Hello World!"
|
||||
auto res = cli.Get("/hi");
|
||||
res->status;
|
||||
res->body;
|
||||
```
|
||||
|
||||
### Try out the examples on Repl.it!
|
||||
@@ -120,24 +133,30 @@ svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
|
||||
|
||||
The followings are built-in mappings:
|
||||
|
||||
| Extension | MIME Type |
|
||||
| :-------- | :--------------------- |
|
||||
| txt | text/plain |
|
||||
| html, htm | text/html |
|
||||
| css | text/css |
|
||||
| jpeg, jpg | image/jpg |
|
||||
| png | image/png |
|
||||
| gif | image/gif |
|
||||
| svg | image/svg+xml |
|
||||
| ico | image/x-icon |
|
||||
| json | application/json |
|
||||
| pdf | application/pdf |
|
||||
| js | application/javascript |
|
||||
| wasm | application/wasm |
|
||||
| xml | application/xml |
|
||||
| xhtml | application/xhtml+xml |
|
||||
| Extension | MIME Type | Extension | MIME Type |
|
||||
| :--------- | :-------------------------- | :--------- | :-------------------------- |
|
||||
| css | text/css | mpga | audio/mpeg |
|
||||
| csv | text/csv | weba | audio/webm |
|
||||
| txt | text/plain | wav | audio/wave |
|
||||
| vtt | text/vtt | otf | font/otf |
|
||||
| html, htm | text/html | ttf | font/ttf |
|
||||
| apng | image/apng | woff | font/woff |
|
||||
| avif | image/avif | woff2 | font/woff2 |
|
||||
| bmp | image/bmp | 7z | application/x-7z-compressed |
|
||||
| gif | image/gif | atom | application/atom+xml |
|
||||
| png | image/png | pdf | application/pdf |
|
||||
| svg | image/svg+xml | mjs, js | application/javascript |
|
||||
| webp | image/webp | json | application/json |
|
||||
| ico | image/x-icon | rss | application/rss+xml |
|
||||
| tif | image/tiff | tar | application/x-tar |
|
||||
| tiff | image/tiff | xhtml, xht | application/xhtml+xml |
|
||||
| jpeg, jpg | image/jpeg | xslt | application/xslt+xml |
|
||||
| mp4 | video/mp4 | xml | application/xml |
|
||||
| mpeg | video/mpeg | gz | application/gzip |
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||
|
||||
NOTE: These the static file server methods are not thread safe.
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### Logging
|
||||
|
||||
@@ -158,6 +177,26 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Pre routing handler
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto& req, auto& res) -> bool {
|
||||
if (req.path == "/hello") {
|
||||
res.set_content("world", "text/html");
|
||||
return true; // This request is handled
|
||||
}
|
||||
return false; // Let the router handle this request
|
||||
});
|
||||
```
|
||||
|
||||
### Post routing handler
|
||||
|
||||
```cpp
|
||||
svr.set_post_routing_handler([](const auto& req, auto& res) {
|
||||
res.set_header("ADDITIONAL_HEADER", "value");
|
||||
});
|
||||
```
|
||||
|
||||
### 'multipart/form-data' POST data
|
||||
|
||||
```cpp
|
||||
@@ -171,7 +210,7 @@ svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### Receive content with Content receiver
|
||||
### Receive content with a content receiver
|
||||
|
||||
```cpp
|
||||
svr.Post("/content_receiver",
|
||||
@@ -198,7 +237,7 @@ svr.Post("/content_receiver",
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
### Send content with the content provider
|
||||
|
||||
```cpp
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
@@ -242,6 +281,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
@@ -255,7 +295,7 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
@@ -275,6 +315,7 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
|
||||
```cpp
|
||||
svr.set_keep_alive_max_count(2); // Default is 5
|
||||
svr.set_keep_alive_timeout(10); // Default is 5
|
||||
```
|
||||
|
||||
### Timeout
|
||||
@@ -285,7 +326,7 @@ svr.set_write_timeout(5, 0); // 5 seconds
|
||||
svr.set_idle_interval(0, 100000); // 100 milliseconds
|
||||
```
|
||||
|
||||
### Set maximum payload length for reading request body
|
||||
### Set maximum payload length for reading a request body
|
||||
|
||||
```c++
|
||||
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
|
||||
@@ -363,6 +404,28 @@ httplib::Client cli("localhost:8080");
|
||||
httplib::Client cli("http://localhost");
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::Client cli("https://localhost");
|
||||
httplib::SSLClient cli("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
|
||||
@@ -449,7 +512,7 @@ cli.set_read_timeout(5, 0); // 5 seconds
|
||||
cli.set_write_timeout(5, 0); // 5 seconds
|
||||
```
|
||||
|
||||
### Receive content with Content receiver
|
||||
### Receive content with a content receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
@@ -476,12 +539,12 @@ auto res = cli.Get(
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
### Send content with a content provider
|
||||
|
||||
```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);
|
||||
@@ -490,6 +553,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
|
||||
@@ -584,7 +662,7 @@ res = cli.Get("/");
|
||||
res->status; // 200
|
||||
```
|
||||
|
||||
### Use a specitic network interface
|
||||
### Use a specific network interface
|
||||
|
||||
NOTE: This feature is not available on Windows, yet.
|
||||
|
||||
@@ -592,27 +670,10 @@ NOTE: This feature is not available on Windows, yet.
|
||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||
```
|
||||
|
||||
OpenSSL Support
|
||||
---------------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
Compression
|
||||
-----------
|
||||
|
||||
The server can applie compression to the following MIME type contents:
|
||||
The server can apply compression to the following MIME type contents:
|
||||
|
||||
* all text types except text/event-stream
|
||||
* image/svg+xml
|
||||
@@ -645,6 +706,32 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res->body; // Compressed data
|
||||
```
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// Server
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234");
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
@@ -676,7 +763,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: Cygwin on Windows is not supported.
|
||||
Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
PUT /search/sample?a=12 HTTP/1.1
|
||||
@@ -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
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <httplib.h>
|
||||
#include <memory>
|
||||
|
||||
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 static_cast<ssize_t>(size);
|
||||
}
|
||||
|
||||
ssize_t write(const char *ptr, size_t size) override {
|
||||
response_.append(ptr, size);
|
||||
return static_cast<int>(size);
|
||||
}
|
||||
|
||||
ssize_t write(const char *ptr) { return write(ptr, strlen(ptr)); }
|
||||
|
||||
ssize_t 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -42,8 +42,9 @@
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-conversion"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#endif
|
||||
|
||||
// The following lines pull in the real gtest *.cc files.
|
||||
@@ -9127,5 +9128,5 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames(
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
+7
-6
@@ -63,8 +63,9 @@
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-compare"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#endif
|
||||
|
||||
// Copyright 2005, Google Inc.
|
||||
@@ -18353,8 +18354,8 @@ AssertionResult CmpHelperEQ(const char* expected_expression,
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-compare"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#endif
|
||||
|
||||
if (expected == actual) {
|
||||
@@ -18366,7 +18367,7 @@ AssertionResult CmpHelperEQ(const char* expected_expression,
|
||||
#elif __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
return EqFailure(expected_expression,
|
||||
@@ -19564,7 +19565,7 @@ bool StaticAssertTypeEq() {
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // GTEST_INCLUDE_GTEST_GTEST_H_
|
||||
|
||||
+586
-47
@@ -2,11 +2,13 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
#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,33 @@ TEST(StartupTest, WSAStartup) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||
string unescapedCharacters = "-_.!~*'()";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(unescapedCharacters), "-_.!~*'()");
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseReservedCharactersTest) {
|
||||
string reservedCharacters = ";,/?:@&=+$";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(reservedCharacters),
|
||||
"%3B%2C%2F%3F%3A%40%26%3D%2B%24");
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, TestUTF8Characters) {
|
||||
string chineseCharacters = "中国語";
|
||||
string russianCharacters = "дом";
|
||||
string brazilianCharacters = "óculos";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(chineseCharacters),
|
||||
"%E4%B8%AD%E5%9B%BD%E8%AA%9E");
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(russianCharacters),
|
||||
"%D0%B4%D0%BE%D0%BC");
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(brazilianCharacters), "%C3%B3culos");
|
||||
}
|
||||
|
||||
TEST(TrimTests, TrimStringTests) {
|
||||
EXPECT_EQ("abc", detail::trim_copy("abc"));
|
||||
EXPECT_EQ("abc", detail::trim_copy(" abc "));
|
||||
@@ -134,6 +163,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 =
|
||||
@@ -692,9 +732,8 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(AbsoluteRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -703,13 +742,13 @@ TEST(AbsoluteRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/absolute-redirect/3");
|
||||
auto res = cli.Get("/httpbin/absolute-redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -718,13 +757,13 @@ TEST(RedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/3");
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RelativeRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -733,13 +772,13 @@ TEST(RelativeRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/relative-redirect/3");
|
||||
auto res = cli.Get("/httpbin/relative-redirect/3");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(TooManyRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
@@ -748,11 +787,10 @@ TEST(TooManyRedirectTest, Redirect) {
|
||||
#endif
|
||||
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/21");
|
||||
auto res = cli.Get("/httpbin/redirect/21");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::ExceedRedirectCount, res.error());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(YahooRedirectTest, Redirect) {
|
||||
@@ -766,18 +804,17 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("https://yahoo.com/", res->location);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
auto res = cli.Get(
|
||||
"/httpbin/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
@@ -829,7 +866,7 @@ TEST(UrlWithSpace, Redirect) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Server, BindDualStack) {
|
||||
TEST(BindServerTest, BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -862,7 +899,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());
|
||||
@@ -871,7 +908,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");
|
||||
@@ -881,6 +918,137 @@ 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());
|
||||
}
|
||||
|
||||
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
res.set_header("PRE_ROUTING", "on");
|
||||
res.set_content("Routing Handler", "text/plain");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Error", "text/html");
|
||||
});
|
||||
|
||||
svr.set_post_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
res.set_header("POST_ROUTING", "on");
|
||||
}
|
||||
});
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
});
|
||||
|
||||
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("/routing_handler");
|
||||
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("on", res->get_header_value("PRE_ROUTING"));
|
||||
EXPECT_EQ(1, res->get_header_value_count("POST_ROUTING"));
|
||||
EXPECT_EQ("on", res->get_header_value("POST_ROUTING"));
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
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"));
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/aaa");
|
||||
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"));
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(InvalidFormatTest, StatusCode) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
res.status = 9999; // Status should be a three-digit code...
|
||||
});
|
||||
|
||||
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_FALSE(res);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1287,6 +1455,38 @@ protected:
|
||||
std::string url = "/redirect/" + std::to_string(num);
|
||||
res.set_redirect(url);
|
||||
})
|
||||
.Post("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Put("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Patch("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
.Delete("/binary",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(4, req.body.size());
|
||||
EXPECT_EQ("application/octet-stream",
|
||||
req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4", req.get_header_value("Content-Length"));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
})
|
||||
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
|
||||
.Get("/compress",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -1387,6 +1587,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) {
|
||||
@@ -1615,6 +1816,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) {
|
||||
@@ -1625,10 +1827,72 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
|
||||
EXPECT_EQ("abcde", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRange) {
|
||||
auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(206, res->status);
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("cd"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, Binary) {
|
||||
std::vector<char> binary{0x00, 0x01, 0x02, 0x03};
|
||||
|
||||
auto res = cli_.Post("/binary", binary.data(), binary.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, 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());
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, BinaryString) {
|
||||
auto binary = std::string("\x00\x01\x02\x03", 4);
|
||||
|
||||
auto res = cli_.Post("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, 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());
|
||||
|
||||
res = cli_.Patch("/binary", binary, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ(4, 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());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, EmptyRequest) {
|
||||
auto res = cli_.Get("");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -1708,7 +1972,8 @@ TEST_F(ServerTest, LongHeader) {
|
||||
"@@@@@@@@@@@@@@@@");
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
auto error = Error::Success;
|
||||
auto ret = cli_.send(req, *res, error);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1768,7 +2033,8 @@ TEST_F(ServerTest, TooLongHeader) {
|
||||
"@@@@@@@@@@@@@@@@@");
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
auto error = Error::Success;
|
||||
auto ret = cli_.send(req, *res, error);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1857,7 +2123,8 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding) {
|
||||
req.body = "4\r\ndech\r\nf\r\nunked post body\r\n0\r\n\r\n";
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
auto error = Error::Success;
|
||||
auto ret = cli_.send(req, *res, error);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1897,6 +2164,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}})}});
|
||||
@@ -1932,8 +2234,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));
|
||||
@@ -1979,6 +2280,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);
|
||||
@@ -1988,6 +2295,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);
|
||||
@@ -2027,7 +2341,8 @@ TEST_F(ServerTest, LargeChunkedPost) {
|
||||
req.body = chunk + chunk + chunk + chunk + chunk + chunk + "0\r\n\r\n";
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
auto error = Error::Success;
|
||||
auto ret = cli_.send(req, *res, error);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2070,9 +2385,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));
|
||||
@@ -2118,6 +2438,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);
|
||||
@@ -2148,6 +2493,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");
|
||||
@@ -2235,7 +2607,7 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
#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);
|
||||
@@ -2245,7 +2617,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);
|
||||
@@ -2396,7 +2768,8 @@ TEST_F(ServerTest, HTTP2Magic) {
|
||||
req.body = "SM";
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
auto error = Error::Success;
|
||||
auto ret = cli_.send(req, *res, error);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(400, res->status);
|
||||
@@ -2594,7 +2967,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"
|
||||
@@ -2607,7 +2980,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;
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, false, nullptr,
|
||||
@@ -2797,6 +3170,12 @@ TEST(ServerRequestParsingTest, InvalidHeaderTextWithExtraCR) {
|
||||
"Content-Type: text/plain\r\n\r");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
|
||||
std::string out;
|
||||
test_raw_request("GET /h i HTTP/1.1\r\n\r\n", &out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -2923,9 +3302,9 @@ TEST(MountTest, Unmount) {
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
// res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -2986,6 +3365,81 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_error_handler([](Request const &, Response &res) -> void {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain", [](std::size_t const, DataSink &sink) -> bool {
|
||||
sink.os << "hello";
|
||||
sink.os << "world";
|
||||
sink.done();
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("helloworld", res->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
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()
|
||||
@@ -3120,6 +3574,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");
|
||||
@@ -3151,6 +3618,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");
|
||||
|
||||
@@ -3205,6 +3697,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) || defined(OPENSSL_USE_APPLINK)
|
||||
TEST(SSLClientServerTest, MemoryClientCertPresent) {
|
||||
X509 *server_cert;
|
||||
EVP_PKEY *server_private_key;
|
||||
@@ -3280,6 +3773,7 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
|
||||
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SSLClientServerTest, ClientCertMissing) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
@@ -3324,6 +3818,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
|
||||
@@ -3333,17 +3872,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) {
|
||||
@@ -3362,6 +3899,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) {
|
||||
@@ -3375,6 +3913,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) {
|
||||
@@ -3398,13 +3937,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);
|
||||
@@ -3414,15 +3954,14 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
|
||||
Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
Client("https://httpbin.org")
|
||||
.set_follow_location(true)
|
||||
.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
cli.Get("/httpbin/"
|
||||
"redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -16,3 +16,6 @@ emailAddress = test@email.address
|
||||
|
||||
[req_attributes]
|
||||
challengePassword = 1234
|
||||
|
||||
[SAN]
|
||||
subjectAltName=IP:127.0.0.1
|
||||
|
||||
+27
-30
@@ -8,29 +8,29 @@ using namespace httplib;
|
||||
template <typename T>
|
||||
void ProxyTest(T& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(407, res->status);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(ProxyTest, SSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
ProxyTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, SSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
ProxyTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
@@ -54,29 +54,27 @@ void RedirectProxyText(T& cli, const char *path, bool basic) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(RedirectTest, HTTPBinNoSSLBasic) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
Client cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", true);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, HTTPBinNoSSLDigest) {
|
||||
Client cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
Client cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", false);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", true);
|
||||
SSLClient cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinSSLDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
RedirectProxyText(cli, "/redirect/2", false);
|
||||
SSLClient cli("nghttp2.org");
|
||||
RedirectProxyText(cli, "/httpbin/redirect/2", false);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, YouTubeNoSSLBasic) {
|
||||
@@ -218,7 +216,8 @@ TEST(DigestAuthTest, NoSSL) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void KeepAliveTest(Client& cli, bool basic) {
|
||||
template <typename T>
|
||||
void KeepAliveTest(T& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
@@ -234,20 +233,20 @@ void KeepAliveTest(Client& cli, bool basic) {
|
||||
#endif
|
||||
|
||||
{
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
{
|
||||
auto res = cli.Get("/redirect/2");
|
||||
auto res = cli.Get("/httpbin/redirect/2");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<std::string> paths = {
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
"/httpbin/digest-auth/auth/hello/world/MD5",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-256",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-512",
|
||||
"/httpbin/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
for (auto path: paths) {
|
||||
@@ -258,34 +257,32 @@ void KeepAliveTest(Client& cli, bool basic) {
|
||||
}
|
||||
|
||||
{
|
||||
int count = 100;
|
||||
int count = 10;
|
||||
while (count--) {
|
||||
auto res = cli.Get("/get");
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, NoSSLWithBasic) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithBasic) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
KeepAliveTest(cli, true);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, NoSSLWithDigest) {
|
||||
Client cli("httpbin.org");
|
||||
Client cli("nghttp2.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLWithDigest) {
|
||||
SSLClient cli("httpbin.org");
|
||||
SSLClient cli("nghttp2.org");
|
||||
KeepAliveTest(cli, false);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user