Compare commits

...

91 Commits

Author SHA1 Message Date
Eric Lee 8d9a477edb No content check (#823)
* No content check

* unit test for no content

* fixing merge conflict break

* oops during manual merge conflict
2021-01-13 17:51:40 -05:00
yhirose 85b4abbf16 Updated the simple example 2021-01-07 19:56:33 -05:00
yhirose e42a358da8 Updated README 2021-01-07 19:00:24 -05:00
yhirose f008fe4539 Added middleware support (#816) 2021-01-07 18:40:52 -05:00
yhirose ddf41d29ef Added const *char and size_t interface 2021-01-06 22:39:58 -05:00
yhirose 3f88a46c4a Code format 2020-12-31 18:14:28 -05:00
yhirose 242706ea34 Fix #820 2020-12-31 18:14:05 -05:00
yhirose a9f5f8683f Fixed warnings on Visual C++ 2020-12-31 11:35:11 -05:00
yhirose 60c2213893 Fix #817 2020-12-31 10:58:44 -05:00
yhirose eb2d28bca2 Code cleanup 2020-12-31 10:37:06 -05:00
Yuri Santos 86f637a246 Added encode_uri_param tests with UTF-8 characters (#818)
Remove additional lines between tests
2020-12-30 23:06:36 -05:00
yhirose 2c07ec4600 Code cleanup 2020-12-29 09:39:19 -05:00
yhirose 871d8d67b0 Made Request paramater const in handle_file_request 2020-12-28 22:03:44 -05:00
Anonymous 7299713195 Fix readme Response::set_chunked_content_provider (#811)
`Response::set_chunked_content_provider` has formal parameters, not one.
2020-12-25 19:55:57 -05:00
yhirose 96afa7e108 Updated README 2020-12-21 13:40:32 -05:00
yhirose 55f57af0b9 Update README 2020-12-21 09:27:36 -05:00
yhirose 6b35cd0116 Updated README 2020-12-21 08:15:06 -05:00
yhirose 99f2229e48 Updated README 2020-12-19 22:43:31 -05:00
yhirose b9641048fc Switch to hghttp2.org/httpbin for redirect test. (#538) 2020-12-19 20:27:52 -05:00
yhirose e9c6c6e609 Code format 2020-12-19 20:14:53 -05:00
yhirose 40db42108f Fixed problem with invalid requests including spaces in URL path 2020-12-19 12:03:08 -05:00
Anonymous 24bb1387d6 Update README.md (#806) 2020-12-19 11:12:44 -05:00
Jeremie Rahm d0bd4afb0b Ensure socket is closed after processing in SSLServer (#804) 2020-12-18 19:29:36 -05:00
Yuri Santos 78ea786abd [PR] Special function to encode query params (#801)
* Special function to encode query params

* Fix #include <iomanip>

* Added unescaped charsets to encode_query_param

* Unit tests for encode_query_param
2020-12-18 17:51:11 -05:00
Miosame 9cac2c9ceb typo: specitic => specific (#802) 2020-12-18 15:12:21 -05:00
Anonymous 0cff3245df Extend built-in extension MIME mapping (#799)
* Update README.md

* Update httplib.h

* Update httplib.h

* Update httplib.h

* Update httplib.h

* Remove duplicate cases

Someone left a bunch of duplicate cases, idiot, couldn't have been me.

* Reformat

Modify spacing and whatnot

* Update README.md
2020-12-18 09:32:19 -05:00
yhirose 0e3925db3f Fixed build error 2020-12-18 00:07:48 +00:00
yhirose c9a13d214b Changed not to use string_view 2020-12-17 18:48:27 -05:00
yhirose 0954af2d4c Use user-defined literals for file extention match 2020-12-17 18:27:04 -05:00
yhirose 7c1c952f5a Don't allow invalid status code format (It sould be a three-digit code.) 2020-12-15 20:25:24 -05:00
yhirose a6edfc730a Added a unit test for static file with range 2020-12-15 18:47:51 -05:00
yhirose c1264bfedc Fix problem with mp4 w/ Range header 2020-12-14 22:41:05 -05:00
yhirose 90a5b6ceb0 Updated README 2020-12-04 19:39:39 -05:00
yhirose eb240ad2e5 Code cleanup 2020-12-03 16:03:12 -05:00
yhirose 88c961f37e Removed std::atomic<Error> error_ 2020-12-01 15:17:34 +00:00
yhirose b952376968 Fixed warning 2020-12-01 03:50:55 +00:00
yhirose 5dd605d3a2 Fix #762 2020-11-30 21:49:31 -05:00
Seunghwan Hong 9c0c98b1ed Add keep_alive_timeout guide on README.md (#778) 2020-11-30 07:11:22 -05:00
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
16 changed files with 2694 additions and 860 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: |
+135 -48
View File
@@ -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
-------
+1484 -722
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
+88
View File
@@ -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;
}
+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;
}
+4 -3
View File
@@ -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
View File
@@ -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_
+611 -47
View File
@@ -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,162 @@ 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(NoContentTest, ContentLength) {
Server svr;
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.status = 204;
});
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(204, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
}
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 +1480,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 +1612,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 +1841,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 +1852,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 +1997,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 +2058,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 +2148,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 +2189,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 +2259,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 +2305,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 +2320,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 +2366,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 +2410,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 +2463,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 +2518,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 +2632,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 +2642,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 +2793,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 +2992,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 +3005,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 +3195,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 +3327,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 +3390,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 +3599,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 +3643,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 +3722,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 +3798,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 +3843,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 +3897,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 +3924,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 +3938,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 +3962,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 +3979,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
+3
View File
@@ -16,3 +16,6 @@ emailAddress = test@email.address
[req_attributes]
challengePassword = 1234
[SAN]
subjectAltName=IP:127.0.0.1
+27 -30
View File
@@ -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