mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da746c6e67 | |||
| 3451da940d | |||
| 38a6b3e69f | |||
| d1037ee9fd | |||
| 2ece5f116b | |||
| c2b6e4ac04 | |||
| 8674555b88 | |||
| 85327e19ae | |||
| ed8efea98b | |||
| 1ccddd1b0b | |||
| 992f3dc690 | |||
| 402d47e2cd | |||
| 171fc2e353 | |||
| ced4160d05 | |||
| 5b51aa6851 | |||
| dc13cde820 | |||
| b0af78e340 | |||
| 914c8860e8 | |||
| dc6a72a0fd | |||
| 685533ba50 | |||
| 6e46ccb37c | |||
| ac18b70a0f | |||
| e1acb949e7 | |||
| ab96f49766 | |||
| 7b3cea5317 | |||
| 26deffe0c6 | |||
| e07c5fec01 | |||
| 6e473a7c5c | |||
| c74129a1c2 | |||
| 18e750b4e7 | |||
| bf7700d192 | |||
| 3da925d6fe | |||
| 319417f26d | |||
| 4c3b119dde | |||
| 6de8684328 | |||
| ccc9a9b3f4 | |||
| f2bb9c45d6 | |||
| 9a663aa94e | |||
| d0d744d520 | |||
| fce8e6fefd | |||
| 180aa32ebf | |||
| fe01fa760b | |||
| d61d63dd97 | |||
| 3fe13ecc91 | |||
| 064cc6810e | |||
| 464cc89b77 | |||
| ca5a50d2c9 | |||
| b251668522 | |||
| 9ee740fe8f | |||
| 851edaf77f | |||
| 1a2a6e2b01 | |||
| ac7742bb32 | |||
| 82c11168c1 | |||
| 7c5197c86c | |||
| 8801e51138 | |||
| 89740a808d | |||
| 2377a20e0b | |||
| 5e43680486 | |||
| 79df842cc5 | |||
| 48a4a5812e | |||
| a7a6df49a4 | |||
| e932c7e1ec |
@@ -1,29 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.7.0)
|
||||
project(httplib)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
# Include
|
||||
include(GNUInstallDirs)
|
||||
include(ExternalProject)
|
||||
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibConfig
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
||||
|
||||
install(EXPORT httplibConfig DESTINATION share/httplib/cmake)
|
||||
|
||||
export(TARGETS ${PROJECT_NAME} FILE httplibConfig.cmake)
|
||||
|
||||
#add_subdirectory(example)
|
||||
#add_subdirectory(test)
|
||||
@@ -9,6 +9,8 @@ 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!
|
||||
|
||||
For Windows users: Please read [this note](https://github.com/yhirose/cpp-httplib#windows).
|
||||
|
||||
Server Example
|
||||
--------------
|
||||
|
||||
@@ -17,24 +19,34 @@ Server Example
|
||||
|
||||
int main(void)
|
||||
{
|
||||
using namespace httplib;
|
||||
using namespace httplib;
|
||||
|
||||
Server svr;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
svr.Get("/body-header-param", [](const Request& req, Response& res) {
|
||||
if (req.has_header("Content-Length")) {
|
||||
auto val = req.get_header_value("Content-Length");
|
||||
}
|
||||
if (req.has_param("key")) {
|
||||
auto val = req.get_param_value("key");
|
||||
}
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,17 +62,24 @@ svr.listen_after_bind();
|
||||
### Static File Server
|
||||
|
||||
```cpp
|
||||
auto ret = svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
|
||||
// Mount / to ./www directory
|
||||
auto ret = svr.set_mount_point("/", "./www");
|
||||
if (!ret) {
|
||||
// The specified base directory doesn't exist...
|
||||
}
|
||||
|
||||
// Mount /public to ./www directory
|
||||
ret = svr.set_base_dir("./www", "/public");
|
||||
ret = svr.set_mount_point("/public", "./www");
|
||||
|
||||
// Mount /public to ./www1 and ./www2 directories
|
||||
ret = svr.set_base_dir("./www1", "/public"); // 1st order to search
|
||||
ret = svr.set_base_dir("./www2", "/public"); // 2nd order to search
|
||||
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
|
||||
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
|
||||
|
||||
// Remove mount /
|
||||
ret = svr.remove_mount_point("/");
|
||||
|
||||
// Remove mount /public
|
||||
ret = svr.remove_mount_point("/public");
|
||||
```
|
||||
|
||||
```cpp
|
||||
@@ -72,39 +91,41 @@ 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 |
|
||||
| :-------- | :--------------------- |
|
||||
| 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 |
|
||||
|
||||
NOTE: These the static file server methods are not thread safe.
|
||||
|
||||
### Logging
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto& req, const auto& res) {
|
||||
your_logger(req, res);
|
||||
your_logger(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handler
|
||||
### Error handler
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto& req, auto& res) {
|
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -112,31 +133,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_t DATA_CHUNK_SIZE = 4;
|
||||
|
||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
[data] { delete data; });
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
});
|
||||
```
|
||||
|
||||
@@ -167,6 +169,24 @@ svr.Post("/content_receiver",
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_t DATA_CHUNK_SIZE = 4;
|
||||
|
||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
[data] { delete data; });
|
||||
});
|
||||
```
|
||||
|
||||
### Chunked transfer encoding
|
||||
|
||||
```cpp
|
||||
@@ -191,11 +211,7 @@ Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/s
|
||||
|
||||
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||
|
||||
Set thread count to 8:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
|
||||
```
|
||||
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
@@ -223,47 +239,64 @@ svr.new_task_queue = [] {
|
||||
};
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return 417;
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Send a final status without reading the message body.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return res.status = 401;
|
||||
});
|
||||
```
|
||||
|
||||
Client Example
|
||||
--------------
|
||||
|
||||
### GET
|
||||
|
||||
```c++
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
httplib::Client cli("localhost", 1234);
|
||||
// IMPORTANT: 1st parameter must be a hostname or an IP adress string.
|
||||
httplib::Client cli("localhost", 1234);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET with HTTP headers
|
||||
|
||||
```c++
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
|
||||
### GET with Content Receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert(res->body.empty());
|
||||
assert(res->body.empty());
|
||||
```
|
||||
|
||||
### POST
|
||||
@@ -296,15 +329,15 @@ auto res = cli.Post("/post", params);
|
||||
### POST with Multipart Form Data
|
||||
|
||||
```c++
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
|
||||
auto res = cli.Post("/multipart", items);
|
||||
auto res = cli.Post("/multipart", items);
|
||||
```
|
||||
|
||||
### PUT
|
||||
@@ -338,19 +371,17 @@ httplib::Client client(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
std::shared_ptr<httplib::Response> res =
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)((len/total)*100));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)(len*100/total));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||

|
||||
|
||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
||||
|
||||
### Authentication
|
||||
|
||||
```cpp
|
||||
@@ -485,9 +516,33 @@ httplib.h httplib.cc
|
||||
NOTE
|
||||
----
|
||||
|
||||
### g++
|
||||
|
||||
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
|
||||
|
||||
### Windows
|
||||
|
||||
Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand.
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
#include <Windows.h>
|
||||
```
|
||||
|
||||
```cpp
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: Cygwin on Windows is not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2019 Yuji Hirose)
|
||||
MIT license (© 2020 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy!
|
||||
|
||||
@@ -122,12 +122,12 @@ int main(int argc, const char **argv) {
|
||||
auto base_dir = "./";
|
||||
if (argc > 2) { base_dir = argv[2]; }
|
||||
|
||||
if (!svr.set_base_dir(base_dir)) {
|
||||
if (!svr.set_mount_point("/", base_dir)) {
|
||||
cout << "The specified base directory doesn't exist...";
|
||||
return 1;
|
||||
}
|
||||
|
||||
cout << "The server started at port " << port << "...";
|
||||
cout << "The server started at port " << port << "..." << endl;
|
||||
|
||||
svr.listen("localhost", port);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
border = '// ----------------------------------------------------------------------------'
|
||||
|
||||
PythonVersion = sys.version_info[0];
|
||||
|
||||
with open('httplib.h') as f:
|
||||
lines = f.readlines()
|
||||
inImplementation = False
|
||||
os.makedirs('out', exist_ok=True)
|
||||
|
||||
if PythonVersion < 3:
|
||||
os.makedirs('out')
|
||||
else:
|
||||
os.makedirs('out', exist_ok=True)
|
||||
|
||||
with open('out/httplib.h', 'w') as fh:
|
||||
with open('out/httplib.cc', 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
|
||||
OPENSSL_DIR = /usr/local/opt/openssl
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
+14
-1
@@ -38,6 +38,14 @@
|
||||
// when it's fused.
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-conversion"
|
||||
#endif
|
||||
|
||||
// The following lines pull in the real gtest *.cc files.
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
@@ -9039,7 +9047,6 @@ void HasNewFatalFailureHelper::ReportTestPartResult(
|
||||
//
|
||||
// Author: wan@google.com (Zhanyong Wan)
|
||||
|
||||
|
||||
namespace testing {
|
||||
namespace internal {
|
||||
|
||||
@@ -9116,3 +9123,9 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames(
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#endif
|
||||
|
||||
+15
-1
@@ -59,6 +59,14 @@
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic push
|
||||
#pragma gcc diagnostic ignored "-Wsign-compare"
|
||||
#endif
|
||||
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
@@ -7311,7 +7319,7 @@ inline const char* SkipComma(const char* str) {
|
||||
// the entire string if it contains no comma.
|
||||
inline String GetPrefixUntilComma(const char* str) {
|
||||
const char* comma = strchr(str, ',');
|
||||
return comma == NULL ? String(str) : String(str, comma - str);
|
||||
return comma == NULL ? String(str) : String(str, static_cast<size_t>(comma - str));
|
||||
}
|
||||
|
||||
// TypeParameterizedTest<Fixture, TestSel, Types>::Register()
|
||||
@@ -19553,4 +19561,10 @@ bool StaticAssertTypeEq() {
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif __GNUC__
|
||||
#pragma gcc diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // GTEST_INCLUDE_GTEST_GTEST_H_
|
||||
|
||||
+356
-16
@@ -77,6 +77,21 @@ TEST(ParseQueryTest, ParseQueryString) {
|
||||
EXPECT_EQ("val3", dic.find("key3")->second);
|
||||
}
|
||||
|
||||
TEST(ParamsToQueryTest, ConvertParamsToQuery) {
|
||||
Params dic;
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "");
|
||||
|
||||
dic.emplace("key1", "val1");
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1");
|
||||
|
||||
dic.emplace("key2", "val2");
|
||||
dic.emplace("key3", "val3");
|
||||
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
@@ -201,7 +216,7 @@ TEST(ParseHeaderValueTest, Range) {
|
||||
|
||||
TEST(BufferStreamTest, read) {
|
||||
detail::BufferStream strm1;
|
||||
Stream& strm = strm1;
|
||||
Stream &strm = strm1;
|
||||
|
||||
EXPECT_EQ(5, strm.write("hello"));
|
||||
|
||||
@@ -645,11 +660,23 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
|
||||
TEST(Server, BindAndListenSeparately) {
|
||||
Server svr;
|
||||
int port = svr.bind_to_any_port("localhost");
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLServer, BindAndListenSeparately) {
|
||||
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");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -662,14 +689,44 @@ protected:
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
svr_.set_base_dir("./www");
|
||||
svr_.set_base_dir("./www2", "/mount");
|
||||
svr_.set_mount_point("/", "./www");
|
||||
svr_.set_mount_point("/mount", "./www2");
|
||||
svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde");
|
||||
|
||||
svr_.Get("/hi",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
})
|
||||
.Get("/http_response_splitting",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_header("a", "1\nSet-Cookie: a=1");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_header("a", "1\rSet-Cookie: a=1");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_header("a\r\nb", "0");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_header("a\rb", "0");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_header("a\nb", "0");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("a"));
|
||||
|
||||
res.set_redirect("1\r\nSet-Cookie: a=1");
|
||||
EXPECT_EQ(0, res.headers.size());
|
||||
EXPECT_FALSE(res.has_header("Location"));
|
||||
})
|
||||
.Get("/slow",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
@@ -684,8 +741,21 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
})
|
||||
.Get("/a\\+\\+b",
|
||||
[&](const Request &req, Response &res) {
|
||||
ASSERT_TRUE(req.has_param("a +b"));
|
||||
auto val = req.get_param_value("a +b");
|
||||
res.set_content(val, "text/plain");
|
||||
})
|
||||
.Get("/", [&](const Request & /*req*/,
|
||||
Response &res) { res.set_redirect("/hi"); })
|
||||
.Post("/1", [](const Request & /*req*/,
|
||||
Response &res) { res.set_redirect("/2", 303); })
|
||||
.Get("/2",
|
||||
[](const Request & /*req*/, Response &res) {
|
||||
res.set_content("redirected.", "text/plain");
|
||||
res.status = 200;
|
||||
})
|
||||
.Post("/person",
|
||||
[&](const Request &req, Response &res) {
|
||||
if (req.has_param("name") && req.has_param("note")) {
|
||||
@@ -797,6 +867,7 @@ protected:
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_EQ(5u, req.files.size());
|
||||
ASSERT_TRUE(!req.has_file("???"));
|
||||
ASSERT_TRUE(req.body.empty());
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text1");
|
||||
@@ -848,6 +919,11 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("DELETE", "text/plain");
|
||||
})
|
||||
.Delete("/delete-body",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "content");
|
||||
res.set_content(req.body, "text/plain");
|
||||
})
|
||||
.Options(R"(\*)",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Allow", "GET, POST, HEAD, OPTIONS");
|
||||
@@ -1030,6 +1106,16 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("HTTP/1.1", res->version);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod302) {
|
||||
auto res = cli_.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1037,6 +1123,14 @@ TEST_F(ServerTest, GetMethod302) {
|
||||
EXPECT_EQ("/hi", res->get_header_value("Location"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod302Redirect) {
|
||||
cli_.set_follow_location(true);
|
||||
auto res = cli_.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod404) {
|
||||
auto res = cli_.Get("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1051,6 +1145,15 @@ TEST_F(ServerTest, HeadMethod200) {
|
||||
EXPECT_EQ("", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod200Static) {
|
||||
auto res = cli_.Head("/mount/dir/index.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(104, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod404) {
|
||||
auto res = cli_.Head("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1227,6 +1330,21 @@ TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMethod303) {
|
||||
auto res = cli_.Post("/1", "body", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(303, res->status);
|
||||
EXPECT_EQ("/2", res->get_header_value("Location"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMethod303Redirect) {
|
||||
cli_.set_follow_location(true);
|
||||
auto res = cli_.Post("/1", "body", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("redirected.", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
|
||||
auto res = cli_.Get("/dir/test.abcde");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1236,7 +1354,7 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||
EXPECT_EQ(false, svr_.set_base_dir("./www3", "invalid_mount_point"));
|
||||
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, EmptyRequest) {
|
||||
@@ -1413,6 +1531,13 @@ TEST_F(ServerTest, EndWithPercentCharacterInQuery) {
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PlusSignEncoding) {
|
||||
auto res = cli_.Get("/a+%2Bb?a %2bb=a %2Bb");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("a +b", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, MultipartFormData) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
@@ -1620,6 +1745,12 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
|
||||
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HTTPResponseSplitting) {
|
||||
auto res = cli_.Get("/http_response_splitting");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, SlowRequest) {
|
||||
request_threads_.push_back(
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
@@ -1675,6 +1806,20 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutContentWithDeflate) {
|
||||
cli_.set_compress(false);
|
||||
httplib::Headers headers;
|
||||
headers.emplace("Content-Encoding", "deflate");
|
||||
// PUT in deflate format:
|
||||
auto res = cli_.Put("/put", headers,
|
||||
"\170\234\013\010\015\001\0\001\361\0\372", "text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_F(ServerTest, Patch) {
|
||||
@@ -1691,6 +1836,13 @@ TEST_F(ServerTest, Delete) {
|
||||
EXPECT_EQ("DELETE", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, DeleteContentReceiver) {
|
||||
auto res = cli_.Delete("/delete-body", "content", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("content", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, Options) {
|
||||
auto res = cli_.Options("*");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1799,7 +1951,7 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
ASSERT_TRUE(ret == true);
|
||||
ASSERT_TRUE(requests.size() == responses.size());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
auto &res = responses[i];
|
||||
EXPECT_EQ(200, res.status);
|
||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
||||
@@ -1990,7 +2142,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
EXPECT_EQ(header_value, "\v bar \e");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
static void test_raw_request(const std::string &req) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
@@ -2007,19 +2160,60 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// A certain header line causes an exception if the header property is parsed
|
||||
// naively with a single regex. This occurs with libc++ but not libstdc++.
|
||||
const std::string req =
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" ";
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
// A certain header line causes an exception if the header property is parsed
|
||||
// naively with a single regex. This occurs with libc++ but not libstdc++.
|
||||
test_raw_request(
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" "
|
||||
" ");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
// A certain header line causes an exception if the header property *name* is
|
||||
// parsed with a regular expression starting with "(.+?):" - this is a non-
|
||||
// greedy matcher and requires backtracking when there are a lot of ":"
|
||||
// characters.
|
||||
// This occurs with libc++ but not libstdc++.
|
||||
test_raw_request(
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
":-:::::::::::::::::::::::::::-::::::::::::::::::::::::@-&&&&&&&&&&&"
|
||||
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
|
||||
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-:::::"
|
||||
"::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::::::::::::::::::::"
|
||||
":::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::"
|
||||
"::::::::-:::::::::::::::::@-&&&&&&&--:::::::-::::::::::::::::::::::"
|
||||
":::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::"
|
||||
"::::::::::-:::::::::::::::::@-&&&&&::::::::::::-:::::::::::::::::@-"
|
||||
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
|
||||
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
|
||||
"::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::@-&&"
|
||||
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
|
||||
"::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&"
|
||||
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
|
||||
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&"
|
||||
"&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&"
|
||||
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
|
||||
"-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::"
|
||||
"::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::"
|
||||
":::::@-&&&&&&&&&&&::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::"
|
||||
":::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::"
|
||||
"::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-"
|
||||
":::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&---&&:&"
|
||||
"&&.0------------:-:::::::::::::::::::::::::::::-:::::::::::::::::@-"
|
||||
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
|
||||
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
|
||||
"::::@-&&&&&&&&&&&---&&:&&&.0------------O--------\rH PUTHTTP/1.1\r\n"
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -2028,7 +2222,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
res.set_header("Cache-Control", "no-cache");
|
||||
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
|
||||
char buffer[27];
|
||||
int size = sprintf(buffer, "data:%ld\n\n", offset);
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
});
|
||||
@@ -2060,6 +2254,78 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(MountTest, Unmount) {
|
||||
Server svr;
|
||||
|
||||
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);
|
||||
|
||||
svr.set_mount_point("/mount2", "./www2");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
res = cli.Get("/mount2/dir/test.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.set_mount_point("/", "./www");
|
||||
|
||||
res = cli.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.remove_mount_point("/");
|
||||
res = cli.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
svr.remove_mount_point("/mount2");
|
||||
res = cli.Get("/mount2/dir/test.html");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
throw std::runtime_error("exception...");
|
||||
res.set_content("Hello World!", "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));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_FALSE(res->has_header("EXCEPTION_WHAT"));
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -2244,7 +2510,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
char name[BUFSIZ];
|
||||
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
|
||||
name, sizeof(name));
|
||||
common_name.assign(name, name_len);
|
||||
common_name.assign(name, static_cast<size_t>(name_len));
|
||||
}
|
||||
|
||||
EXPECT_EQ("Common Name", common_name);
|
||||
@@ -2264,6 +2530,80 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(SSLClientServerTest, MemoryClientCertPresent) {
|
||||
X509 *server_cert;
|
||||
EVP_PKEY *server_private_key;
|
||||
X509_STORE *client_ca_cert_store;
|
||||
X509 *client_cert;
|
||||
EVP_PKEY *client_private_key;
|
||||
|
||||
FILE *f = fopen(SERVER_CERT_FILE, "r+");
|
||||
server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
|
||||
fclose(f);
|
||||
|
||||
f = fopen(SERVER_PRIVATE_KEY_FILE, "r+");
|
||||
server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
|
||||
fclose(f);
|
||||
|
||||
f = fopen(CLIENT_CA_CERT_FILE, "r+");
|
||||
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
|
||||
client_ca_cert_store = X509_STORE_new();
|
||||
X509_STORE_add_cert(client_ca_cert_store, client_cert);
|
||||
X509_free(client_cert);
|
||||
fclose(f);
|
||||
|
||||
f = fopen(CLIENT_CERT_FILE, "r+");
|
||||
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
|
||||
fclose(f);
|
||||
|
||||
f = fopen(CLIENT_PRIVATE_KEY_FILE, "r+");
|
||||
client_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
|
||||
fclose(f);
|
||||
|
||||
SSLServer svr(server_cert, server_private_key, client_ca_cert_store);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
|
||||
svr.Get("/test", [&](const Request &req, Response &res) {
|
||||
res.set_content("test", "text/plain");
|
||||
svr.stop();
|
||||
ASSERT_TRUE(true);
|
||||
|
||||
auto peer_cert = SSL_get_peer_certificate(req.ssl);
|
||||
ASSERT_TRUE(peer_cert != nullptr);
|
||||
|
||||
auto subject_name = X509_get_subject_name(peer_cert);
|
||||
ASSERT_TRUE(subject_name != nullptr);
|
||||
|
||||
std::string common_name;
|
||||
{
|
||||
char name[BUFSIZ];
|
||||
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
|
||||
name, sizeof(name));
|
||||
common_name.assign(name, static_cast<size_t>(name_len));
|
||||
}
|
||||
|
||||
EXPECT_EQ("Common Name", common_name);
|
||||
|
||||
X509_free(peer_cert);
|
||||
});
|
||||
|
||||
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, client_cert, client_private_key);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
X509_free(server_cert);
|
||||
EVP_PKEY_free(server_private_key);
|
||||
X509_free(client_cert);
|
||||
EVP_PKEY_free(client_private_key);
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(SSLClientServerTest, ClientCertMissing) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
CLIENT_CA_CERT_DIR);
|
||||
|
||||
Reference in New Issue
Block a user