mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82acdca638 | |||
| a1e56a567b | |||
| 0c17d172a2 | |||
| 5d8e7c761f | |||
| 17fc522b75 | |||
| f1daa5b88b | |||
| fe9a1949a6 | |||
| 50cba6db9f | |||
| 18592e7f98 | |||
| bd9612b81e | |||
| 7ab5fb65b2 | |||
| 8df5fedc35 | |||
| 4a61f68fa4 | |||
| 067890133c | |||
| d3076f5a70 | |||
| ed129f057f | |||
| eab5ea01d7 | |||
| 3e287b3a26 | |||
| 4f33637b43 | |||
| 698a1e51ec | |||
| 27c0e1186c | |||
| c54c71a3e5 |
+28
-18
@@ -9,6 +9,7 @@
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
@@ -74,6 +75,10 @@ string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
@@ -87,6 +92,8 @@ option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable H
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
@@ -113,7 +120,7 @@ elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND)
|
||||
if(OPENSSL_FOUND AND NOT DEFINED HTTPLIB_IS_USING_OPENSSL)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
endif()
|
||||
|
||||
@@ -226,6 +233,7 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
|
||||
# Set the definitions to enable optional features
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
@@ -262,31 +270,33 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Install it so it can be used later by the httplibConfig.cmake file.
|
||||
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
|
||||
@@ -94,11 +94,20 @@ int main(void)
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
// Match the request path against a regular expression
|
||||
// and extract its captures
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
|
||||
// Capture the second segment of the request path as "id" path param
|
||||
svr.Get("/users/:id", [&](const Request& req, Response& res) {
|
||||
auto user_id = req.path_params.at("id");
|
||||
res.set_content(user_id, "text/plain");
|
||||
});
|
||||
|
||||
// Extract values from HTTP headers and URL query params
|
||||
svr.Get("/body-header-param", [](const Request& req, Response& res) {
|
||||
if (req.has_header("Content-Length")) {
|
||||
auto val = req.get_header_value("Content-Length");
|
||||
@@ -181,6 +190,8 @@ The followings are built-in mappings:
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### File request handler
|
||||
|
||||
```cpp
|
||||
@@ -190,8 +201,6 @@ svr.set_file_request_handler([](const Request &req, Response &res) {
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### Logging
|
||||
|
||||
```cpp
|
||||
@@ -303,7 +312,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
"text/plain", // Content type
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
[&, data](size_t offset, size_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true; // return 'false' if you want to cancel the process.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.12.4"
|
||||
#define CPPHTTPLIB_VERSION "0.13.0"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -172,9 +172,15 @@ using socket_t = SOCKET;
|
||||
#else // not _WIN32
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#ifndef _AIX
|
||||
#if !defined(_AIX) && !defined(__MVS__)
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
#ifdef __MVS__
|
||||
#include <strings.h>
|
||||
#ifndef NI_MAXHOST
|
||||
#define NI_MAXHOST 1025
|
||||
#endif
|
||||
#endif
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -223,6 +229,9 @@ using socket_t = int;
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#ifdef _WIN32
|
||||
@@ -465,6 +474,7 @@ struct Request {
|
||||
MultipartFormDataMap files;
|
||||
Ranges ranges;
|
||||
Match matches;
|
||||
std::unordered_map<std::string, std::string> path_params;
|
||||
|
||||
// for client
|
||||
ResponseHandler response_handler;
|
||||
@@ -658,6 +668,76 @@ using SocketOptions = std::function<void(socket_t sock)>;
|
||||
|
||||
void default_socket_options(socket_t sock);
|
||||
|
||||
namespace detail {
|
||||
|
||||
class MatcherBase {
|
||||
public:
|
||||
virtual ~MatcherBase() = default;
|
||||
|
||||
// Match request path and populate its matches and
|
||||
virtual bool match(Request &request) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Captures parameters in request path and stores them in Request::path_params
|
||||
*
|
||||
* Capture name is a substring of a pattern from : to /.
|
||||
* The rest of the pattern is matched agains the request path directly
|
||||
* Parameters are captured starting from the next character after
|
||||
* the end of the last matched static pattern fragment until the next /.
|
||||
*
|
||||
* Example pattern:
|
||||
* "/path/fragments/:capture/more/fragments/:second_capture"
|
||||
* Static fragments:
|
||||
* "/path/fragments/", "more/fragments/"
|
||||
*
|
||||
* Given the following request path:
|
||||
* "/path/fragments/:1/more/fragments/:2"
|
||||
* the resulting capture will be
|
||||
* {{"capture", "1"}, {"second_capture", "2"}}
|
||||
*/
|
||||
class PathParamsMatcher : public MatcherBase {
|
||||
public:
|
||||
PathParamsMatcher(const std::string &pattern);
|
||||
|
||||
bool match(Request &request) const override;
|
||||
|
||||
private:
|
||||
static constexpr char marker = ':';
|
||||
// Treat segment separators as the end of path parameter capture
|
||||
// Does not need to handle query parameters as they are parsed before path
|
||||
// matching
|
||||
static constexpr char separator = '/';
|
||||
|
||||
// Contains static path fragments to match against, excluding the '/' after
|
||||
// path params
|
||||
// Fragments are separated by path params
|
||||
std::vector<std::string> static_fragments_;
|
||||
// Stores the names of the path parameters to be used as keys in the
|
||||
// Request::path_params map
|
||||
std::vector<std::string> param_names_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs std::regex_match on request path
|
||||
* and stores the result in Request::matches
|
||||
*
|
||||
* Note that regex match is performed directly on the whole request.
|
||||
* This means that wildcard patterns may match multiple path segments with /:
|
||||
* "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
|
||||
*/
|
||||
class RegexMatcher : public MatcherBase {
|
||||
public:
|
||||
RegexMatcher(const std::string &pattern) : regex_(pattern) {}
|
||||
|
||||
bool match(Request &request) const override;
|
||||
|
||||
private:
|
||||
std::regex regex_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class Server {
|
||||
public:
|
||||
using Handler = std::function<void(const Request &, Response &)>;
|
||||
@@ -765,9 +845,14 @@ protected:
|
||||
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
||||
|
||||
private:
|
||||
using Handlers = std::vector<std::pair<std::regex, Handler>>;
|
||||
using Handlers =
|
||||
std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
|
||||
using HandlersForContentReader =
|
||||
std::vector<std::pair<std::regex, HandlerWithContentReader>>;
|
||||
std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
|
||||
HandlerWithContentReader>>;
|
||||
|
||||
static std::unique_ptr<detail::MatcherBase>
|
||||
make_matcher(const std::string &pattern);
|
||||
|
||||
socket_t create_server_socket(const std::string &host, int port,
|
||||
int socket_flags,
|
||||
@@ -810,17 +895,18 @@ private:
|
||||
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
|
||||
std::atomic<bool> is_running_{false};
|
||||
std::atomic<bool> done_{false};
|
||||
|
||||
struct MountPointEntry {
|
||||
std::string mount_point;
|
||||
std::string base_dir;
|
||||
Headers headers;
|
||||
};
|
||||
std::vector<MountPointEntry> base_dirs_;
|
||||
|
||||
std::atomic<bool> is_running_{false};
|
||||
std::atomic<bool> done_{false};
|
||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||
Handler file_request_handler_;
|
||||
|
||||
Handlers get_handlers_;
|
||||
Handlers post_handlers_;
|
||||
HandlersForContentReader post_handlers_for_content_reader_;
|
||||
@@ -831,13 +917,15 @@ private:
|
||||
Handlers delete_handlers_;
|
||||
HandlersForContentReader delete_handlers_for_content_reader_;
|
||||
Handlers options_handlers_;
|
||||
|
||||
HandlerWithResponse error_handler_;
|
||||
ExceptionHandler exception_handler_;
|
||||
HandlerWithResponse pre_routing_handler_;
|
||||
Handler post_routing_handler_;
|
||||
Logger logger_;
|
||||
Expect100ContinueHandler expect_100_continue_handler_;
|
||||
|
||||
Logger logger_;
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = default_socket_options;
|
||||
@@ -871,6 +959,7 @@ std::ostream &operator<<(std::ostream &os, const Error &obj);
|
||||
|
||||
class Result {
|
||||
public:
|
||||
Result() = default;
|
||||
Result(std::unique_ptr<Response> &&res, Error err,
|
||||
Headers &&request_headers = Headers{})
|
||||
: res_(std::move(res)), err_(err),
|
||||
@@ -899,7 +988,7 @@ public:
|
||||
|
||||
private:
|
||||
std::unique_ptr<Response> res_;
|
||||
Error err_;
|
||||
Error err_ = Error::Unknown;
|
||||
Headers request_headers_;
|
||||
};
|
||||
|
||||
@@ -1059,12 +1148,14 @@ public:
|
||||
bool send(Request &req, Response &res, Error &error);
|
||||
Result send(const Request &req);
|
||||
|
||||
size_t is_socket_open() const;
|
||||
|
||||
socket_t socket() const;
|
||||
|
||||
void stop();
|
||||
|
||||
std::string host() const;
|
||||
int port() const;
|
||||
|
||||
size_t is_socket_open() const;
|
||||
socket_t socket() const;
|
||||
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
@@ -1117,6 +1208,7 @@ public:
|
||||
void set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||
const std::string &ca_cert_dir_path = std::string());
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size);
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -1431,12 +1523,14 @@ public:
|
||||
bool send(Request &req, Response &res, Error &error);
|
||||
Result send(const Request &req);
|
||||
|
||||
size_t is_socket_open() const;
|
||||
|
||||
socket_t socket() const;
|
||||
|
||||
void stop();
|
||||
|
||||
std::string host() const;
|
||||
int port() const;
|
||||
|
||||
size_t is_socket_open() const;
|
||||
socket_t socket() const;
|
||||
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
@@ -1497,6 +1591,7 @@ public:
|
||||
const std::string &ca_cert_dir_path = std::string());
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
void load_ca_cert_store(const char *ca_cert, std::size_t size);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
@@ -1556,6 +1651,7 @@ public:
|
||||
bool is_valid() const override;
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
void load_ca_cert_store(const char *ca_cert, std::size_t size);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
@@ -1665,17 +1761,17 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
||||
inline void default_socket_options(socket_t sock) {
|
||||
int yes = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
|
||||
reinterpret_cast<char *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
#else
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
|
||||
sizeof(yes));
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
#else
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
|
||||
sizeof(yes));
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -2003,7 +2099,7 @@ inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
|
||||
val = 0;
|
||||
for (; cnt; i++, cnt--) {
|
||||
if (!s[i]) { return false; }
|
||||
int v = 0;
|
||||
auto v = 0;
|
||||
if (is_hex(s[i], v)) {
|
||||
val = val * 16 + v;
|
||||
} else {
|
||||
@@ -2014,7 +2110,7 @@ inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
|
||||
}
|
||||
|
||||
inline std::string from_i_to_hex(size_t n) {
|
||||
const char *charset = "0123456789abcdef";
|
||||
static const auto charset = "0123456789abcdef";
|
||||
std::string ret;
|
||||
do {
|
||||
ret = charset[n & 15] + ret;
|
||||
@@ -2064,8 +2160,8 @@ inline std::string base64_encode(const std::string &in) {
|
||||
std::string out;
|
||||
out.reserve(in.size());
|
||||
|
||||
int val = 0;
|
||||
int valb = -6;
|
||||
auto val = 0;
|
||||
auto valb = -6;
|
||||
|
||||
for (auto c : in) {
|
||||
val = (val << 8) + static_cast<uint8_t>(c);
|
||||
@@ -2196,7 +2292,7 @@ inline std::string decode_url(const std::string &s,
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
if (s[i] == '%' && i + 1 < s.size()) {
|
||||
if (s[i + 1] == 'u') {
|
||||
int val = 0;
|
||||
auto val = 0;
|
||||
if (from_hex_to_i(s, i + 2, 4, val)) {
|
||||
// 4 digits Unicode codes
|
||||
char buff[4];
|
||||
@@ -2207,7 +2303,7 @@ inline std::string decode_url(const std::string &s,
|
||||
result += s[i];
|
||||
}
|
||||
} else {
|
||||
int val = 0;
|
||||
auto val = 0;
|
||||
if (from_hex_to_i(s, i + 1, 2, val)) {
|
||||
// 2 digits hex codes
|
||||
result += static_cast<char>(val);
|
||||
@@ -2354,7 +2450,7 @@ inline int close_socket(socket_t sock) {
|
||||
}
|
||||
|
||||
template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
ssize_t res = false;
|
||||
ssize_t res = 0;
|
||||
while (true) {
|
||||
res = fn();
|
||||
if (res < 0 && errno == EINTR) { continue; }
|
||||
@@ -2458,7 +2554,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
if (poll_res == 0) { return Error::ConnectionTimeout; }
|
||||
|
||||
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
int error = 0;
|
||||
auto error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
@@ -2490,7 +2586,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
if (ret == 0) { return Error::ConnectionTimeout; }
|
||||
|
||||
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
int error = 0;
|
||||
auto error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
@@ -2735,17 +2831,27 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
#endif
|
||||
|
||||
if (tcp_nodelay) {
|
||||
int yes = 1;
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
auto yes = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (socket_options) { socket_options(sock); }
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
int no = 0;
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
|
||||
sizeof(no));
|
||||
auto no = 0;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const char *>(&no), sizeof(no));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const void *>(&no), sizeof(no));
|
||||
#endif
|
||||
}
|
||||
|
||||
// bind or connect
|
||||
@@ -2804,7 +2910,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if !defined _WIN32 && !defined ANDROID && !defined _AIX
|
||||
#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
|
||||
#define USE_IF2IP
|
||||
#endif
|
||||
|
||||
@@ -2888,13 +2994,14 @@ inline socket_t create_client_socket(
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
|
||||
read_timeout_usec / 1000);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
|
||||
sizeof(timeout));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
{
|
||||
@@ -2902,13 +3009,14 @@ inline socket_t create_client_socket(
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
|
||||
write_timeout_usec / 1000);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
|
||||
sizeof(timeout));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3218,7 +3326,7 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length,
|
||||
data += strm_.avail_in;
|
||||
|
||||
auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
|
||||
int ret = Z_OK;
|
||||
auto ret = Z_OK;
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
do {
|
||||
@@ -3262,7 +3370,7 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
Callback callback) {
|
||||
assert(is_valid_);
|
||||
|
||||
int ret = Z_OK;
|
||||
auto ret = Z_OK;
|
||||
|
||||
do {
|
||||
constexpr size_t max_avail_in =
|
||||
@@ -3367,7 +3475,7 @@ inline bool brotli_decompressor::decompress(const char *data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t *next_in = (const uint8_t *)data;
|
||||
auto next_in = reinterpret_cast<const uint8_t *>(data);
|
||||
size_t avail_in = data_length;
|
||||
size_t total_out;
|
||||
|
||||
@@ -3910,7 +4018,8 @@ inline bool redirect(T &cli, Request &req, Response &res,
|
||||
if (ret) {
|
||||
req = new_req;
|
||||
res = new_res;
|
||||
res.location = location;
|
||||
|
||||
if (res.location.empty()) res.location = location;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -4471,7 +4580,7 @@ inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
|
||||
std::stringstream ss;
|
||||
for (auto i = 0u; i < hash_length; ++i) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< (unsigned int)hash[i];
|
||||
<< static_cast<unsigned int>(hash[i]);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
@@ -4564,7 +4673,7 @@ inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
|
||||
|
||||
inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
|
||||
auto result = false;
|
||||
for (int i = 0; i < CFArrayGetCount(certs); ++i) {
|
||||
for (auto i = 0; i < CFArrayGetCount(certs); ++i) {
|
||||
const auto cert = reinterpret_cast<const __SecCertificate *>(
|
||||
CFArrayGetValueAtIndex(certs, i));
|
||||
|
||||
@@ -4782,7 +4891,7 @@ inline void hosted_at(const std::string &hostname,
|
||||
const auto &addr =
|
||||
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
|
||||
std::string ip;
|
||||
int dummy = -1;
|
||||
auto dummy = -1;
|
||||
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
|
||||
dummy)) {
|
||||
addrs.push_back(ip);
|
||||
@@ -5120,6 +5229,99 @@ inline socket_t BufferStream::socket() const { return 0; }
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
|
||||
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
||||
// One past the last ending position of a path param substring
|
||||
std::size_t last_param_end = 0;
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
// Needed to ensure that parameter names are unique during matcher
|
||||
// construction
|
||||
// If exceptions are disabled, only last duplicate path
|
||||
// parameter will be set
|
||||
std::unordered_set<std::string> param_name_set;
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
const auto marker_pos = pattern.find(marker, last_param_end);
|
||||
if (marker_pos == std::string::npos) { break; }
|
||||
|
||||
static_fragments_.push_back(
|
||||
pattern.substr(last_param_end, marker_pos - last_param_end));
|
||||
|
||||
const auto param_name_start = marker_pos + 1;
|
||||
|
||||
auto sep_pos = pattern.find(separator, param_name_start);
|
||||
if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
|
||||
|
||||
auto param_name =
|
||||
pattern.substr(param_name_start, sep_pos - param_name_start);
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
if (param_name_set.find(param_name) != param_name_set.cend()) {
|
||||
std::string msg = "Encountered path parameter '" + param_name +
|
||||
"' multiple times in route pattern '" + pattern + "'.";
|
||||
throw std::invalid_argument(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
param_names_.push_back(std::move(param_name));
|
||||
|
||||
last_param_end = sep_pos + 1;
|
||||
}
|
||||
|
||||
if (last_param_end < pattern.length()) {
|
||||
static_fragments_.push_back(pattern.substr(last_param_end));
|
||||
}
|
||||
}
|
||||
|
||||
inline bool PathParamsMatcher::match(Request &request) const {
|
||||
request.matches = {};
|
||||
request.path_params.clear();
|
||||
request.path_params.reserve(param_names_.size());
|
||||
|
||||
// One past the position at which the path matched the pattern last time
|
||||
std::size_t starting_pos = 0;
|
||||
for (size_t i = 0; i < static_fragments_.size(); ++i) {
|
||||
const auto &fragment = static_fragments_[i];
|
||||
|
||||
if (starting_pos + fragment.length() > request.path.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid unnecessary allocation by using strncmp instead of substr +
|
||||
// comparison
|
||||
if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
|
||||
fragment.length()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
starting_pos += fragment.length();
|
||||
|
||||
// Should only happen when we have a static fragment after a param
|
||||
// Example: '/users/:id/subscriptions'
|
||||
// The 'subscriptions' fragment here does not have a corresponding param
|
||||
if (i >= param_names_.size()) { continue; }
|
||||
|
||||
auto sep_pos = request.path.find(separator, starting_pos);
|
||||
if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
|
||||
|
||||
const auto ¶m_name = param_names_[i];
|
||||
|
||||
request.path_params.emplace(
|
||||
param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
|
||||
|
||||
// Mark everythin up to '/' as matched
|
||||
starting_pos = sep_pos + 1;
|
||||
}
|
||||
// Returns false if the path is longer than the pattern
|
||||
return starting_pos >= request.path.length();
|
||||
}
|
||||
|
||||
inline bool RegexMatcher::match(Request &request) const {
|
||||
request.path_params.clear();
|
||||
return std::regex_match(request.path, request.matches, regex_);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// HTTP server implementation
|
||||
@@ -5133,67 +5335,76 @@ inline Server::Server()
|
||||
|
||||
inline Server::~Server() {}
|
||||
|
||||
inline std::unique_ptr<detail::MatcherBase>
|
||||
Server::make_matcher(const std::string &pattern) {
|
||||
if (pattern.find("/:") != std::string::npos) {
|
||||
return detail::make_unique<detail::PathParamsMatcher>(pattern);
|
||||
} else {
|
||||
return detail::make_unique<detail::RegexMatcher>(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
inline Server &Server::Get(const std::string &pattern, Handler handler) {
|
||||
get_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Post(const std::string &pattern, Handler handler) {
|
||||
post_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Post(const std::string &pattern,
|
||||
HandlerWithContentReader handler) {
|
||||
post_handlers_for_content_reader_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Put(const std::string &pattern, Handler handler) {
|
||||
put_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Put(const std::string &pattern,
|
||||
HandlerWithContentReader handler) {
|
||||
put_handlers_for_content_reader_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
|
||||
patch_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Patch(const std::string &pattern,
|
||||
HandlerWithContentReader handler) {
|
||||
patch_handlers_for_content_reader_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
|
||||
delete_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Delete(const std::string &pattern,
|
||||
HandlerWithContentReader handler) {
|
||||
delete_handlers_for_content_reader_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::Options(const std::string &pattern, Handler handler) {
|
||||
options_handlers_.push_back(
|
||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
||||
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -5272,7 +5483,6 @@ inline Server &Server::set_logger(Logger logger) {
|
||||
inline Server &
|
||||
Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
|
||||
expect_100_continue_handler_ = std::move(handler);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -5789,13 +5999,14 @@ inline bool Server::listen_internal() {
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
|
||||
read_timeout_usec_ / 1000);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
|
||||
sizeof(timeout));
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
{
|
||||
@@ -5803,13 +6014,14 @@ inline bool Server::listen_internal() {
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
|
||||
write_timeout_usec_ / 1000);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
|
||||
sizeof(timeout));
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5829,7 +6041,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
}
|
||||
|
||||
// File handler
|
||||
bool is_head_request = req.method == "HEAD";
|
||||
auto is_head_request = req.method == "HEAD";
|
||||
if ((req.method == "GET" || is_head_request) &&
|
||||
handle_file_request(req, res, is_head_request)) {
|
||||
return true;
|
||||
@@ -5902,10 +6114,10 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
inline bool Server::dispatch_request(Request &req, Response &res,
|
||||
const Handlers &handlers) {
|
||||
for (const auto &x : handlers) {
|
||||
const auto &pattern = x.first;
|
||||
const auto &matcher = x.first;
|
||||
const auto &handler = x.second;
|
||||
|
||||
if (std::regex_match(req.path, req.matches, pattern)) {
|
||||
if (matcher->match(req)) {
|
||||
handler(req, res);
|
||||
return true;
|
||||
}
|
||||
@@ -6027,10 +6239,10 @@ inline bool Server::dispatch_request_for_content_reader(
|
||||
Request &req, Response &res, ContentReader content_reader,
|
||||
const HandlersForContentReader &handlers) {
|
||||
for (const auto &x : handlers) {
|
||||
const auto &pattern = x.first;
|
||||
const auto &matcher = x.first;
|
||||
const auto &handler = x.second;
|
||||
|
||||
if (std::regex_match(req.path, req.matches, pattern)) {
|
||||
if (matcher->match(req)) {
|
||||
handler(req, res, content_reader);
|
||||
return true;
|
||||
}
|
||||
@@ -6801,7 +7013,6 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
req.set_header("Transfer-Encoding", "chunked");
|
||||
} else {
|
||||
req.body.assign(body, content_length);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7454,13 +7665,6 @@ inline Result ClientImpl::Options(const std::string &path,
|
||||
return send_(std::move(req));
|
||||
}
|
||||
|
||||
inline size_t ClientImpl::is_socket_open() const {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
return socket_.is_open();
|
||||
}
|
||||
|
||||
inline socket_t ClientImpl::socket() const { return socket_.sock; }
|
||||
|
||||
inline void ClientImpl::stop() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
|
||||
@@ -7484,6 +7688,17 @@ inline void ClientImpl::stop() {
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline std::string ClientImpl::host() const { return host_; }
|
||||
|
||||
inline int ClientImpl::port() const { return port_; }
|
||||
|
||||
inline size_t ClientImpl::is_socket_open() const {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
return socket_.is_open();
|
||||
}
|
||||
|
||||
inline socket_t ClientImpl::socket() const { return socket_.sock; }
|
||||
|
||||
inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
|
||||
connection_timeout_sec_ = sec;
|
||||
connection_timeout_usec_ = usec;
|
||||
@@ -7571,9 +7786,7 @@ inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
|
||||
proxy_digest_auth_username_ = username;
|
||||
proxy_digest_auth_password_ = password;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||
const std::string &ca_cert_dir_path) {
|
||||
ca_cert_file_path_ = ca_cert_file_path;
|
||||
@@ -7585,9 +7798,34 @@ inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
ca_cert_store_ = ca_cert_store;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
|
||||
std::size_t size) {
|
||||
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
||||
if (!mem) return nullptr;
|
||||
|
||||
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
||||
if (!inf) {
|
||||
BIO_free_all(mem);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto cts = X509_STORE_new();
|
||||
if (cts) {
|
||||
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
||||
auto itmp = sk_X509_INFO_value(inf, i);
|
||||
if (!itmp) { continue; }
|
||||
|
||||
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
||||
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
||||
}
|
||||
}
|
||||
|
||||
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
||||
BIO_free_all(mem);
|
||||
return cts;
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
}
|
||||
@@ -7651,7 +7889,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
|
||||
U ssl_connect_or_accept,
|
||||
time_t timeout_sec,
|
||||
time_t timeout_usec) {
|
||||
int res = 0;
|
||||
auto res = 0;
|
||||
while ((res = ssl_connect_or_accept(ssl)) != 1) {
|
||||
auto err = SSL_get_error(ssl, res);
|
||||
switch (err) {
|
||||
@@ -7732,7 +7970,7 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
auto n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
|
||||
(err == SSL_ERROR_SYSCALL &&
|
||||
@@ -7765,7 +8003,7 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
auto ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
auto n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
|
||||
(err == SSL_ERROR_SYSCALL &&
|
||||
@@ -7820,8 +8058,9 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
|
||||
// add default password callback before opening encrypted private key
|
||||
if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
|
||||
SSL_CTX_set_default_passwd_cb_userdata(ctx_,
|
||||
(char *)private_key_password);
|
||||
SSL_CTX_set_default_passwd_cb_userdata(
|
||||
ctx_,
|
||||
reinterpret_cast<void *>(const_cast<char *>(private_key_password)));
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
|
||||
@@ -7985,6 +8224,11 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void SSLClient::load_ca_cert_store(const char *ca_cert,
|
||||
std::size_t size) {
|
||||
set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size));
|
||||
}
|
||||
|
||||
inline long SSLClient::get_openssl_verify_result() const {
|
||||
return verify_result_;
|
||||
}
|
||||
@@ -8130,6 +8374,10 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl2) {
|
||||
// NOTE: With -Wold-style-cast, this can produce a warning, since
|
||||
// SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
|
||||
// an old style cast. Short of doing compiler specific pragma's
|
||||
// here, we can't get rid of this warning. :'(
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
return true;
|
||||
});
|
||||
@@ -8230,8 +8478,9 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
|
||||
auto val = sk_GENERAL_NAME_value(alt_names, i);
|
||||
if (val->type == type) {
|
||||
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
|
||||
auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
|
||||
auto name =
|
||||
reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
|
||||
auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
|
||||
|
||||
switch (type) {
|
||||
case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
|
||||
@@ -8249,7 +8498,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
if (dsn_matched || ip_matched) { ret = true; }
|
||||
}
|
||||
|
||||
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
|
||||
GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
|
||||
reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -8661,12 +8911,16 @@ inline bool Client::send(Request &req, Response &res, Error &error) {
|
||||
|
||||
inline Result Client::send(const Request &req) { return cli_->send(req); }
|
||||
|
||||
inline void Client::stop() { cli_->stop(); }
|
||||
|
||||
inline std::string Client::host() const { return cli_->host(); }
|
||||
|
||||
inline int Client::port() const { return cli_->port(); }
|
||||
|
||||
inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
|
||||
|
||||
inline socket_t Client::socket() const { return cli_->socket(); }
|
||||
|
||||
inline void Client::stop() { cli_->stop(); }
|
||||
|
||||
inline void
|
||||
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
|
||||
cli_->set_hostname_addr_map(std::move(addr_map));
|
||||
@@ -8750,7 +9004,9 @@ inline void Client::enable_server_certificate_verification(bool enabled) {
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
|
||||
inline void Client::set_logger(Logger logger) {
|
||||
cli_->set_logger(std::move(logger));
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||
@@ -8766,6 +9022,10 @@ inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
|
||||
set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size));
|
||||
}
|
||||
|
||||
inline long Client::get_openssl_verify_result() const {
|
||||
if (is_ssl_) {
|
||||
return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
|
||||
|
||||
+326
-47
@@ -526,7 +526,13 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
|
||||
}
|
||||
|
||||
TEST(RangeTest, FromHTTPBin_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/range/32"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/range/32"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
@@ -538,7 +544,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -546,7 +552,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
|
||||
{
|
||||
Headers headers = {make_range_header({{1, -1}})};
|
||||
auto res = cli.Get("/range/32", headers);
|
||||
auto res = cli.Get(path, headers);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
@@ -554,7 +560,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
|
||||
{
|
||||
Headers headers = {make_range_header({{1, 10}})};
|
||||
auto res = cli.Get("/range/32", headers);
|
||||
auto res = cli.Get(path, headers);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
@@ -562,7 +568,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
|
||||
{
|
||||
Headers headers = {make_range_header({{0, 31}})};
|
||||
auto res = cli.Get("/range/32", headers);
|
||||
auto res = cli.Get(path, headers);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -570,7 +576,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
|
||||
{
|
||||
Headers headers = {make_range_header({{0, -1}})};
|
||||
auto res = cli.Get("/range/32", headers);
|
||||
auto res = cli.Get(path, headers);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -578,7 +584,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
|
||||
|
||||
{
|
||||
Headers headers = {make_range_header({{0, 32}})};
|
||||
auto res = cli.Get("/range/32", headers);
|
||||
auto res = cli.Get(path, headers);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(416, res->status);
|
||||
}
|
||||
@@ -673,7 +679,13 @@ TEST(ConnectionErrorTest, Timeout_Online) {
|
||||
}
|
||||
|
||||
TEST(CancelTest, NoCancel_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/range/32"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/range/32"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
@@ -684,14 +696,20 @@ TEST(CancelTest, NoCancel_Online) {
|
||||
#endif
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
|
||||
auto res = cli.Get(path, [](uint64_t, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(CancelTest, WithCancelSmallPayload_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/range/32"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/range/32"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
@@ -701,14 +719,20 @@ TEST(CancelTest, WithCancelSmallPayload_Online) {
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
|
||||
auto res = cli.Get(path, [](uint64_t, uint64_t) { return false; });
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
}
|
||||
|
||||
TEST(CancelTest, WithCancelLargePayload_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/range/65536"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/range/65536"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
@@ -720,14 +744,20 @@ TEST(CancelTest, WithCancelLargePayload_Online) {
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
uint32_t count = 0;
|
||||
auto res = cli.Get("/range/65536",
|
||||
[&count](uint64_t, uint64_t) { return (count++ == 0); });
|
||||
auto res =
|
||||
cli.Get(path, [&count](uint64_t, uint64_t) { return (count++ == 0); });
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
}
|
||||
|
||||
TEST(BaseAuthTest, FromHTTPWatch_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/basic-auth/hello/world"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/basic-auth/hello/world"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto port = 443;
|
||||
@@ -738,14 +768,14 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
|
||||
#endif
|
||||
|
||||
{
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/basic-auth/hello/world",
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
auto res =
|
||||
cli.Get(path, {make_basic_authentication_header("hello", "world")});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
@@ -754,7 +784,7 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
|
||||
|
||||
{
|
||||
cli.set_basic_auth("hello", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
@@ -763,14 +793,14 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
|
||||
|
||||
{
|
||||
cli.set_basic_auth("hello", "bad");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("bad", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
@@ -778,26 +808,39 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto unauth_path = std::string{"/digest-auth/auth/hello/world"};
|
||||
auto paths = std::vector<std::string>{
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto unauth_path = std::string{"/httpbin/digest-auth/auth/hello/world"};
|
||||
auto paths = std::vector<std::string>{
|
||||
"/httpbin/digest-auth/auth/hello/world/MD5",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-256",
|
||||
"/httpbin/digest-auth/auth/hello/world/SHA-512",
|
||||
"/httpbin/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
#endif
|
||||
|
||||
auto port = 443;
|
||||
SSLClient cli(host, port);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/digest-auth/auth/hello/world");
|
||||
auto res = cli.Get(unauth_path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<std::string> paths = {
|
||||
"/digest-auth/auth/hello/world/MD5",
|
||||
"/digest-auth/auth/hello/world/SHA-256",
|
||||
"/digest-auth/auth/hello/world/SHA-512",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
cli.set_digest_auth("hello", "world");
|
||||
for (auto path : paths) {
|
||||
for (const auto &path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
@@ -806,7 +849,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
}
|
||||
|
||||
cli.set_digest_auth("hello", "bad");
|
||||
for (auto path : paths) {
|
||||
for (const auto &path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(401, res->status);
|
||||
@@ -815,7 +858,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// for (const auto& path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
// ASSERT_TRUE(res);
|
||||
// EXPECT_EQ(400, res->status);
|
||||
@@ -929,7 +972,7 @@ TEST(YahooRedirectTest, Redirect_Online) {
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("https://yahoo.com/", res->location);
|
||||
EXPECT_EQ("https://www.yahoo.com/", res->location);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect_Online) {
|
||||
@@ -3919,16 +3962,16 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
|
||||
svr.Get("/events", [](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Cache-Control", "no-cache");
|
||||
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
|
||||
DataSink &sink) {
|
||||
std::string s = "data:";
|
||||
s += std::to_string(offset);
|
||||
s += "\n\n";
|
||||
auto ret = sink.write(s.data(), s.size());
|
||||
EXPECT_TRUE(ret);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream", [](size_t offset, DataSink &sink) {
|
||||
std::string s = "data:";
|
||||
s += std::to_string(offset);
|
||||
s += "\n\n";
|
||||
auto ret = sink.write(s.data(), s.size());
|
||||
EXPECT_TRUE(ret);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -4336,6 +4379,12 @@ TEST(GetWithParametersTest, GetWithParameters) {
|
||||
EXPECT_EQ("bar", req.get_param_value("param2"));
|
||||
});
|
||||
|
||||
svr.Get("/users/:id", [&](const Request &req, Response &) {
|
||||
EXPECT_EQ("user-id", req.path_params.at("id"));
|
||||
EXPECT_EQ("foo", req.get_param_value("param1"));
|
||||
EXPECT_EQ("bar", req.get_param_value("param2"));
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
@@ -4376,6 +4425,15 @@ TEST(GetWithParametersTest, GetWithParameters) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/users/user-id?param1=foo¶m2=bar");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
@@ -4414,19 +4472,32 @@ TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
}
|
||||
|
||||
TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) {
|
||||
Client cli("httpbin.org");
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/range/32"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/range/32"};
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.set_default_headers({make_range_header({{1, 10}})});
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
@@ -4637,6 +4708,26 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HostAndPortPropertiesTest, NoSSL) {
|
||||
httplib::Client cli("www.google.com", 1234);
|
||||
ASSERT_EQ("www.google.com", cli.host());
|
||||
ASSERT_EQ(1234, cli.port());
|
||||
}
|
||||
|
||||
TEST(HostAndPortPropertiesTest, NoSSLWithSimpleAPI) {
|
||||
httplib::Client cli("www.google.com:1234");
|
||||
ASSERT_EQ("www.google.com", cli.host());
|
||||
ASSERT_EQ(1234, cli.port());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(HostAndPortPropertiesTest, SSL) {
|
||||
httplib::SSLClient cli("www.google.com");
|
||||
ASSERT_EQ("www.google.com", cli.host());
|
||||
ASSERT_EQ(443, cli.port());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLClientTest, UpdateCAStore) {
|
||||
httplib::SSLClient httplib_client("www.google.com");
|
||||
@@ -4652,8 +4743,16 @@ TEST(SSLClientTest, UpdateCAStore) {
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerNameIndication_Online) {
|
||||
SSLClient cli("httpbin.org", 443);
|
||||
auto res = cli.Get("/get");
|
||||
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
|
||||
auto host = "httpbin.org";
|
||||
auto path = std::string{"/get"};
|
||||
#else
|
||||
auto host = "nghttp2.org";
|
||||
auto path = std::string{"/httpbin/get"};
|
||||
#endif
|
||||
|
||||
SSLClient cli(host, 443);
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
@@ -4710,6 +4809,49 @@ TEST(SSLClientTest, ServerCertificateVerification4) {
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerCertificateVerification5_Online) {
|
||||
std::string cert;
|
||||
detail::read_file(CA_CERT_FILE, cert);
|
||||
|
||||
SSLClient cli("google.com");
|
||||
cli.load_ca_cert_store(cert.data(), cert.size());
|
||||
const auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerCertificateVerification6_Online) {
|
||||
// clang-format off
|
||||
static constexpr char cert[] =
|
||||
"GlobalSign Root CA\n"
|
||||
"==================\n"
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx\n"
|
||||
"GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds\n"
|
||||
"b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV\n"
|
||||
"BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD\n"
|
||||
"VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa\n"
|
||||
"DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc\n"
|
||||
"THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb\n"
|
||||
"Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP\n"
|
||||
"c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX\n"
|
||||
"gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
|
||||
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF\n"
|
||||
"AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj\n"
|
||||
"Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG\n"
|
||||
"j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH\n"
|
||||
"hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC\n"
|
||||
"X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
// clang-format on
|
||||
|
||||
SSLClient cli("google.com");
|
||||
cli.load_ca_cert_store(cert, sizeof(cert));
|
||||
const auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
SSLClient cli("www.youtube.com");
|
||||
|
||||
@@ -5194,7 +5336,7 @@ TEST(YahooRedirectTest2, SimpleInterface_Online) {
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("https://yahoo.com/", res->location);
|
||||
EXPECT_EQ("https://www.yahoo.com/", res->location);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTest3, SimpleInterface_Online) {
|
||||
@@ -6121,19 +6263,19 @@ TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
|
||||
TEST(VulnerabilityTest, CRLFInjection) {
|
||||
Server svr;
|
||||
|
||||
svr.Post("/test1", [](const Request &/*req*/, Response &res) {
|
||||
svr.Post("/test1", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello 1", "text/plain");
|
||||
});
|
||||
|
||||
svr.Delete("/test2", [](const Request &/*req*/, Response &res) {
|
||||
svr.Delete("/test2", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello 2", "text/plain");
|
||||
});
|
||||
|
||||
svr.Put("/test3", [](const Request &/*req*/, Response &res) {
|
||||
svr.Put("/test3", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello 3", "text/plain");
|
||||
});
|
||||
|
||||
svr.Patch("/test4", [](const Request &/*req*/, Response &res) {
|
||||
svr.Patch("/test4", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello 4", "text/plain");
|
||||
});
|
||||
|
||||
@@ -6163,3 +6305,140 @@ TEST(VulnerabilityTest, CRLFInjection) {
|
||||
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, StaticMatch) {
|
||||
const auto pattern = "/users/all";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/all";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, StaticMismatch) {
|
||||
const auto pattern = "/users/all";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/1";
|
||||
ASSERT_FALSE(matcher.match(request));
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, SingleParamInTheMiddle) {
|
||||
const auto pattern = "/users/:id/subscriptions";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/42/subscriptions";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, SingleParamInTheEnd) {
|
||||
const auto pattern = "/users/:id";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/24";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {{"id", "24"}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, SingleParamInTheEndTrailingSlash) {
|
||||
const auto pattern = "/users/:id/";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/42/";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, EmptyParam) {
|
||||
const auto pattern = "/users/:id/";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users//";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {{"id", ""}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, FragmentMismatch) {
|
||||
const auto pattern = "/users/:id/";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/admins/24/";
|
||||
ASSERT_FALSE(matcher.match(request));
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, ExtraFragments) {
|
||||
const auto pattern = "/users/:id";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/42/subscriptions";
|
||||
ASSERT_FALSE(matcher.match(request));
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, MissingTrailingParam) {
|
||||
const auto pattern = "/users/:id";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users";
|
||||
ASSERT_FALSE(matcher.match(request));
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, MissingParamInTheMiddle) {
|
||||
const auto pattern = "/users/:id/subscriptions";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/subscriptions";
|
||||
ASSERT_FALSE(matcher.match(request));
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, MultipleParams) {
|
||||
const auto pattern = "/users/:userid/subscriptions/:subid";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/users/42/subscriptions/2";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {
|
||||
{"userid", "42"}, {"subid", "2"}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(PathParamsTest, SequenceOfParams) {
|
||||
const auto pattern = "/values/:x/:y/:z";
|
||||
detail::PathParamsMatcher matcher(pattern);
|
||||
|
||||
Request request;
|
||||
request.path = "/values/1/2/3";
|
||||
ASSERT_TRUE(matcher.match(request));
|
||||
|
||||
std::unordered_map<std::string, std::string> expected_params = {
|
||||
{"x", "1"}, {"y", "2"}, {"z", "3"}};
|
||||
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user