mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 776b3ffbf9 | |||
| a061b97677 | |||
| d359e3a5f7 | |||
| 5928e0af1a | |||
| a5005789ff | |||
| fae30af47d | |||
| 2feea0c9ab | |||
| a2e4af54b7 | |||
| d0b123be26 | |||
| df138366e4 | |||
| c49441ae64 | |||
| e1506fa186 | |||
| ad9fd3bd93 | |||
| 05e0253195 | |||
| da26b517a3 | |||
| 2b7a968468 | |||
| 240cc85ccb | |||
| 129e2f00b8 | |||
| da746c6e67 | |||
| 3451da940d | |||
| 38a6b3e69f | |||
| d1037ee9fd | |||
| 2ece5f116b | |||
| c2b6e4ac04 | |||
| 8674555b88 | |||
| 85327e19ae | |||
| ed8efea98b | |||
| 1ccddd1b0b | |||
| 992f3dc690 |
@@ -3,6 +3,7 @@ tags
|
||||
example/server
|
||||
example/client
|
||||
example/hello
|
||||
example/simplecli
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
|
||||
@@ -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)
|
||||
@@ -535,6 +535,8 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: Cygwin on Windows is not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
||||
+12
-7
@@ -1,9 +1,14 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2017
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Release;Platform=%PLATFORM%
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Debug;Platform=Win32
|
||||
test_script:
|
||||
- cmd: Debug\test.exe
|
||||
- cmd: x64\Release\test.exe
|
||||
|
||||
+5
-2
@@ -5,7 +5,7 @@ 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
|
||||
|
||||
all: server client hello simplesvr upload redirect sse benchmark
|
||||
all: server client hello simplecli simplesvr upload redirect sse benchmark
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
@@ -16,6 +16,9 @@ client : client.cc ../httplib.h Makefile
|
||||
hello : hello.cc ../httplib.h Makefile
|
||||
$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
simplecli : simplecli.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
simplesvr : simplesvr.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
@@ -36,4 +39,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplesvr upload redirect sse benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect sse benchmark *.pem
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ int main(void) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.listen("localhost", 8080);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// simplecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
#define CA_CERT_FILE "./ca-bundle.crt"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(void) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
httplib::url::Options options;
|
||||
options.ca_cert_file_path = CA_CERT_FILE;
|
||||
// options.server_certificate_verification = true;
|
||||
|
||||
auto res = httplib::url::Get("https://localhost:8080/hi", options);
|
||||
#else
|
||||
auto res = httplib::url::Get("http://localhost:8080/hi");
|
||||
#endif
|
||||
|
||||
if (res) {
|
||||
cout << res->status << endl;
|
||||
cout << res->get_header_value("Content-Type") << endl;
|
||||
cout << res->body << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -134,6 +134,7 @@ using socket_t = int;
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -174,7 +175,6 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Declaration
|
||||
*/
|
||||
@@ -262,6 +262,9 @@ struct Request {
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
std::string remote_addr;
|
||||
int remote_port = -1;
|
||||
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
@@ -312,7 +315,7 @@ struct Response {
|
||||
void set_header(const char *key, const char *val);
|
||||
void set_header(const char *key, const std::string &val);
|
||||
|
||||
void set_redirect(const char *url);
|
||||
void set_redirect(const char *url, int status = 302);
|
||||
void set_content(const char *s, size_t n, const char *content_type);
|
||||
void set_content(std::string s, const char *content_type);
|
||||
|
||||
@@ -352,7 +355,7 @@ public:
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
virtual std::string get_remote_addr() const = 0;
|
||||
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
ssize_t write_format(const char *fmt, const Args &... args);
|
||||
@@ -364,8 +367,11 @@ class TaskQueue {
|
||||
public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle(){};
|
||||
};
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
@@ -462,6 +468,7 @@ public:
|
||||
Server &Patch(const char *pattern, Handler handler);
|
||||
Server &Patch(const char *pattern, HandlerWithContentReader handler);
|
||||
Server &Delete(const char *pattern, Handler handler);
|
||||
Server &Delete(const char *pattern, HandlerWithContentReader handler);
|
||||
Server &Options(const char *pattern, Handler handler);
|
||||
|
||||
[[deprecated]] bool set_base_dir(const char *dir,
|
||||
@@ -551,6 +558,7 @@ private:
|
||||
Handlers patch_handlers_;
|
||||
HandlersForContentReader patch_handlers_for_content_reader_;
|
||||
Handlers delete_handlers_;
|
||||
HandlersForContentReader delete_handlers_for_content_reader_;
|
||||
Handlers options_handlers_;
|
||||
Handler error_handler_;
|
||||
Logger logger_;
|
||||
@@ -602,6 +610,8 @@ public:
|
||||
|
||||
std::shared_ptr<Response> Head(const char *path, const Headers &headers);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -629,6 +639,8 @@ public:
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const MultipartFormDataItems &items);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -686,6 +698,8 @@ public:
|
||||
bool send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses);
|
||||
|
||||
void stop();
|
||||
|
||||
void set_timeout_sec(time_t timeout_sec);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec);
|
||||
@@ -718,6 +732,8 @@ protected:
|
||||
bool process_request(Stream &strm, const Request &req, Response &res,
|
||||
bool last_connection, bool &connection_close);
|
||||
|
||||
std::atomic<socket_t> sock_;
|
||||
|
||||
const std::string host_;
|
||||
const int port_;
|
||||
const std::string host_and_port_;
|
||||
@@ -829,7 +845,7 @@ inline void Post(std::vector<Request> &requests, const char *path,
|
||||
req.method = "POST";
|
||||
req.path = path;
|
||||
req.headers = headers;
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
req.body = body;
|
||||
requests.emplace_back(std::move(req));
|
||||
}
|
||||
@@ -846,6 +862,9 @@ public:
|
||||
const char *client_ca_cert_file_path = nullptr,
|
||||
const char *client_ca_cert_dir_path = nullptr);
|
||||
|
||||
SSLServer(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store = nullptr);
|
||||
|
||||
~SSLServer() override;
|
||||
|
||||
bool is_valid() const override;
|
||||
@@ -863,6 +882,9 @@ public:
|
||||
const std::string &client_cert_path = std::string(),
|
||||
const std::string &client_key_path = std::string());
|
||||
|
||||
SSLClient(const std::string &host, int port, X509 *client_cert,
|
||||
EVP_PKEY *client_key);
|
||||
|
||||
~SSLClient() override;
|
||||
|
||||
bool is_valid() const override;
|
||||
@@ -870,11 +892,13 @@ public:
|
||||
void set_ca_cert_path(const char *ca_ceert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
SSL_CTX *ssl_context() const noexcept;
|
||||
SSL_CTX *ssl_context() const;
|
||||
|
||||
private:
|
||||
bool process_and_close_socket(
|
||||
@@ -895,6 +919,7 @@ private:
|
||||
|
||||
std::string ca_cert_file_path_;
|
||||
std::string ca_cert_dir_path_;
|
||||
X509_STORE *ca_cert_store_ = nullptr;
|
||||
bool server_certificate_verification_ = false;
|
||||
long verify_result_ = 0;
|
||||
};
|
||||
@@ -1268,7 +1293,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1287,7 +1312,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1306,7 +1331,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
const std::string &get_buffer() const;
|
||||
|
||||
@@ -1539,21 +1564,32 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::string get_remote_addr(socket_t sock) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
|
||||
if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()),
|
||||
nullptr, 0, NI_NUMERICHOST)) {
|
||||
return ipstr.data();
|
||||
}
|
||||
inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
|
||||
socklen_t addr_len, std::string &ip,
|
||||
int &port) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
port =
|
||||
ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
}
|
||||
|
||||
return std::string();
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
|
||||
0, NI_NUMERICHOST)) {
|
||||
ip = ipstr.data();
|
||||
}
|
||||
}
|
||||
|
||||
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len)) {
|
||||
get_remote_ip_and_port(addr, addr_len, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
inline const char *
|
||||
@@ -1878,9 +1914,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
auto chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
unsigned long chunk_len;
|
||||
while (true) {
|
||||
char *end_ptr;
|
||||
|
||||
chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
|
||||
|
||||
if (end_ptr == line_reader.ptr()) { return false; }
|
||||
if (chunk_len == ULONG_MAX) { return false; }
|
||||
|
||||
if (chunk_len == 0) { break; }
|
||||
|
||||
while (chunk_len > 0) {
|
||||
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1890,8 +1934,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
}
|
||||
|
||||
if (chunk_len == 0) {
|
||||
@@ -2046,6 +2088,12 @@ inline bool redirect(T &cli, const Request &req, Response &res,
|
||||
new_req.path = path;
|
||||
new_req.redirect_count -= 1;
|
||||
|
||||
if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
|
||||
new_req.method = "GET";
|
||||
new_req.body.clear();
|
||||
new_req.headers.clear();
|
||||
}
|
||||
|
||||
Response new_res;
|
||||
|
||||
auto ret = cli.send(new_req, new_res);
|
||||
@@ -2515,13 +2563,22 @@ get_range_offset_and_length(const Request &req, const Response &res,
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI") {
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
return true;
|
||||
}
|
||||
// TODO: check if Content-Length is set
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool has_crlf(const char *s) {
|
||||
auto p = s;
|
||||
while (*p) {
|
||||
if (*p == '\r' || *p == '\n') { return true; }
|
||||
p++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
template <typename CTX, typename Init, typename Update, typename Final>
|
||||
inline std::string message_digest(const std::string &s, Init init,
|
||||
@@ -2708,11 +2765,15 @@ inline size_t Request::get_header_value_count(const char *key) const {
|
||||
}
|
||||
|
||||
inline void Request::set_header(const char *key, const char *val) {
|
||||
headers.emplace(key, val);
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Request::set_header(const char *key, const std::string &val) {
|
||||
headers.emplace(key, val);
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool Request::has_param(const char *key) const {
|
||||
@@ -2762,16 +2823,26 @@ inline size_t Response::get_header_value_count(const char *key) const {
|
||||
}
|
||||
|
||||
inline void Response::set_header(const char *key, const char *val) {
|
||||
headers.emplace(key, val);
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_header(const char *key, const std::string &val) {
|
||||
headers.emplace(key, val);
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_redirect(const char *url) {
|
||||
set_header("Location", url);
|
||||
status = 302;
|
||||
inline void Response::set_redirect(const char *url, int status) {
|
||||
if (!detail::has_crlf(url)) {
|
||||
set_header("Location", url);
|
||||
if (300 <= status && status < 400) {
|
||||
this->status = status;
|
||||
} else {
|
||||
this->status = 302;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_content(const char *s, size_t n,
|
||||
@@ -2860,25 +2931,42 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
inline SocketStream::~SocketStream() {}
|
||||
|
||||
inline bool SocketStream::is_readable() const {
|
||||
return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
return detail::select_write(sock_, 0, 0) > 0;
|
||||
return select_write(sock_, 0, 0) > 0;
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
if (is_readable()) { return recv(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return recv(sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (is_writable()) { return send(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return send(sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string SocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
return detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
// Buffer stream implementation
|
||||
@@ -2901,7 +2989,8 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
|
||||
return static_cast<ssize_t>(size);
|
||||
}
|
||||
|
||||
inline std::string BufferStream::get_remote_addr() const { return ""; }
|
||||
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
|
||||
int & /*port*/) const {}
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
|
||||
@@ -2968,6 +3057,13 @@ inline Server &Server::Delete(const char *pattern, Handler handler) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Delete(const char *pattern,
|
||||
HandlerWithContentReader handler) {
|
||||
delete_handlers_for_content_reader_.push_back(
|
||||
std::make_pair(std::regex(pattern), handler));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Options(const char *pattern, Handler handler) {
|
||||
options_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
|
||||
return *this;
|
||||
@@ -3374,17 +3470,16 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) {
|
||||
if (svr_sock_ == INVALID_SOCKET) { return -1; }
|
||||
|
||||
if (port == 0) {
|
||||
struct sockaddr_storage address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
|
||||
&len) == -1) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (address.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
|
||||
} else if (address.ss_family == AF_INET6) {
|
||||
return ntohs(
|
||||
reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
|
||||
if (addr.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -3409,6 +3504,7 @@ inline bool Server::listen_internal() {
|
||||
auto val = detail::select_read(svr_sock_, 0, 100000);
|
||||
|
||||
if (val == 0) { // Timeout
|
||||
task_queue->on_idle();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3480,6 +3576,11 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
req, res, reader, patch_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
}
|
||||
} else if (req.method == "DELETE") {
|
||||
if (dispatch_request_for_content_reader(
|
||||
req, res, reader, delete_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3584,7 +3685,9 @@ Server::process_request(Stream &strm, bool last_connection,
|
||||
connection_close = true;
|
||||
}
|
||||
|
||||
req.set_header("REMOTE_ADDR", strm.get_remote_addr());
|
||||
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
||||
|
||||
if (req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
@@ -3635,7 +3738,7 @@ inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
inline Client::Client(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
: host_(host), port_(port),
|
||||
: sock_(INVALID_SOCKET), host_(host), port_(port),
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
@@ -3671,18 +3774,19 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
|
||||
}
|
||||
|
||||
inline bool Client::send(const Request &req, Response &res) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return error; }
|
||||
if (!connect(sock_, res, error)) { return error; }
|
||||
}
|
||||
#endif
|
||||
|
||||
return process_and_close_socket(
|
||||
sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
sock_, 1,
|
||||
[&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
return handle_request(strm, req, res, last_connection,
|
||||
connection_close);
|
||||
});
|
||||
@@ -3692,18 +3796,18 @@ inline bool Client::send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses) {
|
||||
size_t i = 0;
|
||||
while (i < requests.size()) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
Response res;
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return false; }
|
||||
if (!connect(sock_, res, error)) { return false; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!process_and_close_socket(sock, requests.size() - i,
|
||||
if (!process_and_close_socket(sock_, requests.size() - i,
|
||||
[&](Stream &strm, bool last_connection,
|
||||
bool &connection_close) -> bool {
|
||||
auto &req = requests[i++];
|
||||
@@ -3833,34 +3937,42 @@ inline bool Client::redirect(const Request &req, Response &res) {
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!regex_match(location, m, re)) { return false; }
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
|
||||
auto scheme = is_ssl() ? "https" : "http";
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto next_path = m[3].str();
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
next_port = std::stoi(port_str);
|
||||
} else if (!next_scheme.empty()) {
|
||||
next_port = next_scheme == "https" ? 443 : 80;
|
||||
}
|
||||
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == scheme && next_host == host_) {
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
return detail::redirect(*this, req, res, next_path);
|
||||
} else {
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str());
|
||||
SSLClient cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str());
|
||||
Client cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
}
|
||||
@@ -3970,7 +4082,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) {
|
||||
@@ -4032,17 +4144,16 @@ inline bool Client::process_request(Stream &strm, const Request &req,
|
||||
|
||||
// Body
|
||||
if (req.method != "HEAD" && req.method != "CONNECT") {
|
||||
ContentReceiver out = [&](const char *buf, size_t n) {
|
||||
if (res.body.size() + n > res.body.max_size()) { return false; }
|
||||
res.body.append(buf, n);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (req.content_receiver) {
|
||||
out = [&](const char *buf, size_t n) {
|
||||
return req.content_receiver(buf, n);
|
||||
};
|
||||
}
|
||||
auto out =
|
||||
req.content_receiver
|
||||
? static_cast<ContentReceiver>([&](const char *buf, size_t n) {
|
||||
return req.content_receiver(buf, n);
|
||||
})
|
||||
: static_cast<ContentReceiver>([&](const char *buf, size_t n) {
|
||||
if (res.body.size() + n > res.body.max_size()) { return false; }
|
||||
res.body.append(buf, n);
|
||||
return true;
|
||||
});
|
||||
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
@@ -4163,6 +4274,10 @@ inline std::shared_ptr<Response> Client::Head(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path) {
|
||||
return Post(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4235,6 +4350,10 @@ Client::Post(const char *path, const Headers &headers,
|
||||
return Post(path, headers, body, content_type.c_str());
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path) {
|
||||
return Put(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4352,6 +4471,14 @@ inline std::shared_ptr<Response> Client::Options(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline void Client::stop() {
|
||||
if (sock_ != INVALID_SOCKET) {
|
||||
std::atomic<socket_t> sock(sock_.exchange(INVALID_SOCKET));
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Client::set_timeout_sec(time_t timeout_sec) {
|
||||
timeout_sec_ = timeout_sec;
|
||||
}
|
||||
@@ -4450,8 +4577,8 @@ inline bool process_and_close_socket_ssl(
|
||||
auto count = keep_alive_max_count;
|
||||
while (count > 0 &&
|
||||
(is_client_request ||
|
||||
detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
|
||||
auto last_connection = count == 1;
|
||||
auto connection_close = false;
|
||||
@@ -4468,7 +4595,9 @@ inline bool process_and_close_socket_ssl(
|
||||
}
|
||||
}
|
||||
|
||||
SSL_shutdown(ssl);
|
||||
if (ret) {
|
||||
SSL_shutdown(ssl); // shutdown only if not already closed by remote
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex);
|
||||
SSL_free(ssl);
|
||||
@@ -4495,11 +4624,11 @@ public:
|
||||
private:
|
||||
static void locking_callback(int mode, int type, const char * /*file*/,
|
||||
int /*line*/) {
|
||||
auto &locks = *openSSL_locks_;
|
||||
auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
locks[type].lock();
|
||||
lk.lock();
|
||||
} else {
|
||||
locks[type].unlock();
|
||||
lk.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4560,8 +4689,9 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline std::string SSLSocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
static SSLInit sslinit_;
|
||||
@@ -4607,6 +4737,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
}
|
||||
}
|
||||
|
||||
inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_server_method());
|
||||
|
||||
if (ctx_) {
|
||||
SSL_CTX_set_options(ctx_,
|
||||
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
|
||||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
|
||||
SSL_CTX_free(ctx_);
|
||||
ctx_ = nullptr;
|
||||
} else if (client_ca_cert_store) {
|
||||
|
||||
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
ctx_,
|
||||
SSL_VERIFY_PEER |
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline SSLServer::~SSLServer() {
|
||||
if (ctx_) { SSL_CTX_free(ctx_); }
|
||||
}
|
||||
@@ -4646,6 +4803,24 @@ inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
}
|
||||
}
|
||||
|
||||
inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
X509 *client_cert, EVP_PKEY *client_key)
|
||||
: Client(host, port) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_client_method());
|
||||
|
||||
detail::split(&host_[0], &host_[host_.size()], '.',
|
||||
[&](const char *b, const char *e) {
|
||||
host_components_.emplace_back(std::string(b, e));
|
||||
});
|
||||
if (client_cert != nullptr && client_key != nullptr) {
|
||||
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
|
||||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
|
||||
SSL_CTX_free(ctx_);
|
||||
ctx_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline SSLClient::~SSLClient() {
|
||||
if (ctx_) { SSL_CTX_free(ctx_); }
|
||||
}
|
||||
@@ -4658,6 +4833,10 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
|
||||
}
|
||||
|
||||
inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
if (ca_cert_store) { ca_cert_store_ = ca_cert_store; }
|
||||
}
|
||||
|
||||
inline void SSLClient::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
}
|
||||
@@ -4666,7 +4845,7 @@ inline long SSLClient::get_openssl_verify_result() const {
|
||||
return verify_result_;
|
||||
}
|
||||
|
||||
inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
|
||||
inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
|
||||
|
||||
inline bool SSLClient::process_and_close_socket(
|
||||
socket_t sock, size_t request_count,
|
||||
@@ -4681,14 +4860,19 @@ inline bool SSLClient::process_and_close_socket(
|
||||
true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
|
||||
ctx_, ctx_mutex_,
|
||||
[&](SSL *ssl) {
|
||||
if (ca_cert_file_path_.empty()) {
|
||||
if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) {
|
||||
SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
|
||||
} else {
|
||||
} else if (!ca_cert_file_path_.empty()) {
|
||||
if (!SSL_CTX_load_verify_locations(
|
||||
ctx_, ca_cert_file_path_.c_str(), nullptr)) {
|
||||
return false;
|
||||
}
|
||||
SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
|
||||
} else if (ca_cert_store_ != nullptr) {
|
||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) {
|
||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store_);
|
||||
}
|
||||
SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
|
||||
}
|
||||
|
||||
if (SSL_connect(ssl) != 1) { return false; }
|
||||
@@ -4852,6 +5036,64 @@ inline bool SSLClient::check_host_name(const char *pattern,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace url {
|
||||
|
||||
struct Options {
|
||||
// TODO: support more options...
|
||||
bool follow_location = false;
|
||||
std::string client_cert_path;
|
||||
std::string client_key_path;
|
||||
|
||||
std::string ca_cert_file_path;
|
||||
std::string ca_cert_dir_path;
|
||||
bool server_certificate_verification = false;
|
||||
};
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url, Options &options) {
|
||||
const static std::regex re(
|
||||
R"(^(https?)://([^:/?#]+)(?::(\d+))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(url, m, re)) { return nullptr; }
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = !port_str.empty() ? std::stoi(port_str)
|
||||
: (next_scheme == "https" ? 443 : 80);
|
||||
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
cli.set_ca_cert_path(options.ca_cert_file_path.c_str(),
|
||||
options.ca_cert_dir_path.c_str());
|
||||
cli.enable_server_certificate_verification(
|
||||
options.server_certificate_verification);
|
||||
return cli.Get(next_path.c_str());
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
return cli.Get(next_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url) {
|
||||
Options options;
|
||||
return Get(url, options);
|
||||
}
|
||||
|
||||
} // namespace url
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
+330
-19
@@ -649,12 +649,78 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTestWithURL, Redirect) {
|
||||
auto res = httplib::url::Get("http://yahoo.com");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
res = httplib::url::Get("http://yahoo.com", options);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTestWithURL, Redirect) {
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
auto res = httplib::url::Get(
|
||||
"https://httpbin.org/"
|
||||
"redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302",
|
||||
options);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
Server svr8081;
|
||||
|
||||
svr8080.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_redirect("http://localhost:8081/2");
|
||||
});
|
||||
|
||||
svr8081.Get("/2", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto thread8080 = std::thread([&]() { svr8080.listen("localhost", 8080); });
|
||||
|
||||
auto thread8081 = std::thread([&]() { svr8081.listen("localhost", 8081); });
|
||||
|
||||
while (!svr8080.is_running() || !svr8081.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", 8080);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
|
||||
svr8080.stop();
|
||||
svr8081.stop();
|
||||
thread8080.join();
|
||||
thread8081.join();
|
||||
ASSERT_FALSE(svr8080.is_running());
|
||||
ASSERT_FALSE(svr8081.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -697,6 +763,36 @@ protected:
|
||||
[&](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));
|
||||
@@ -705,6 +801,10 @@ protected:
|
||||
.Get("/remote_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
|
||||
EXPECT_TRUE(req.has_header("REMOTE_PORT"));
|
||||
EXPECT_EQ(req.remote_addr, req.get_header_value("REMOTE_ADDR"));
|
||||
EXPECT_EQ(req.remote_port,
|
||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||
res.set_content(remote_addr.c_str(), "text/plain");
|
||||
})
|
||||
.Get("/endwith%",
|
||||
@@ -719,6 +819,13 @@ protected:
|
||||
})
|
||||
.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")) {
|
||||
@@ -834,13 +941,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -853,7 +960,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -861,8 +968,24 @@ protected:
|
||||
.Post("/empty",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("text/plain", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty", "text/plain");
|
||||
})
|
||||
.Post("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_FALSE(req.has_header("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Put("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_FALSE(req.has_header("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Put("/put",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "PUT");
|
||||
@@ -882,6 +1005,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");
|
||||
@@ -928,13 +1056,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -947,7 +1075,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -1012,13 +1140,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("test", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("--abcdefg123", file.content);
|
||||
}
|
||||
})
|
||||
@@ -1081,6 +1209,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);
|
||||
@@ -1092,7 +1228,7 @@ TEST_F(ServerTest, HeadMethod200) {
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod200Static) {
|
||||
@@ -1101,14 +1237,14 @@ TEST_F(ServerTest, HeadMethod200Static) {
|
||||
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);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod404) {
|
||||
auto res = cli_.Head("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodPersonJohn) {
|
||||
@@ -1194,6 +1330,20 @@ TEST_F(ServerTest, PostEmptyContent) {
|
||||
ASSERT_EQ("empty", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Post("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Put("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodDir) {
|
||||
auto res = cli_.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1280,6 +1430,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);
|
||||
@@ -1582,6 +1747,18 @@ TEST_F(ServerTest, GetStreamedEndless) {
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, ClientStop) {
|
||||
thread t = thread([&]() {
|
||||
auto res =
|
||||
cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
cli_.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange1) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1680,6 +1857,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"); }));
|
||||
@@ -1765,6 +1948,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);
|
||||
@@ -1914,7 +2104,7 @@ TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
auto res = cli_.Get("/gzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -1955,7 +2145,7 @@ TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -2015,7 +2205,8 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
#endif
|
||||
|
||||
// Sends a raw request to a server listening at HOST:PORT.
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
std::string *resp = nullptr) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
std::string());
|
||||
|
||||
@@ -2033,7 +2224,9 @@ static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
char buf[512];
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf, sizeof(buf));
|
||||
while (line_reader.getline()) {}
|
||||
while (line_reader.getline()) {
|
||||
if (resp) { *resp += line_reader.ptr(); }
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -2065,11 +2258,15 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
}
|
||||
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
static void test_raw_request(const std::string &req) {
|
||||
static void test_raw_request(const std::string &req,
|
||||
std::string *out = nullptr) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
// Server read timeout must be longer than the client read timeout for the
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
@@ -2082,7 +2279,7 @@ static void test_raw_request(const std::string &req) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req, out));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
@@ -2094,8 +2291,8 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
test_raw_request(
|
||||
"GET /hi HTTP/1.1\r\n"
|
||||
" : "
|
||||
" "
|
||||
);
|
||||
" "
|
||||
" ");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
@@ -2136,6 +2333,46 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"nothex\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidSecondChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"3\r\n"
|
||||
"xyz\r\n"
|
||||
"NaN\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ChunkLengthTooHighInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
// Length is too large for 64 bits.
|
||||
"1ffffffffffffffff\r\n"
|
||||
"xyz\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -2452,6 +2689,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);
|
||||
|
||||
+10
-10
@@ -28,7 +28,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
@@ -40,7 +40,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -69,13 +69,13 @@
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\lib\VC;C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
@@ -84,8 +84,8 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
@@ -118,7 +118,7 @@
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
@@ -160,7 +160,7 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -171,4 +171,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user