//
// httplib.h
//
-// Copyright (c) 2024 Yuji Hirose. All rights reserved.
+// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
-#define CPPHTTPLIB_VERSION "0.15.3"
+#define CPPHTTPLIB_VERSION "0.19.0"
/*
* Configuration
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
+#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
+#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
+#endif
+
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
-#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
+#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
#endif
-#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
-#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
+#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
+#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
+#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
+#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
+#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
#endif
-#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
-#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
+#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
+#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
#endif
-#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
-#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
+#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
+#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
#endif
-#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
-#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
+#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
+#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
+#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
+#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
#endif
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
#define CPPHTTPLIB_TCP_NODELAY false
#endif
+#ifndef CPPHTTPLIB_IPV6_V6ONLY
+#define CPPHTTPLIB_IPV6_V6ONLY false
+#endif
+
#ifndef CPPHTTPLIB_RECV_BUFSIZ
-#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
+#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
#endif
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
+#ifndef __VMS
#include <sys/select.h>
+#endif
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
-#if OPENSSL_VERSION_NUMBER < 0x30000000L
+#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
+#if OPENSSL_VERSION_NUMBER < 0x1010107f
+#error Please use OpenSSL or a current version of BoringSSL
+#endif
+#define SSL_get1_peer_certificate SSL_get_peer_certificate
+#elif OPENSSL_VERSION_NUMBER < 0x30000000L
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
#endif
return std::unique_ptr<T>(new RT[n]);
}
-struct ci {
- bool operator()(const std::string &s1, const std::string &s2) const {
- return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(),
- s2.end(),
- [](unsigned char c1, unsigned char c2) {
- return ::tolower(c1) < ::tolower(c2);
- });
+namespace case_ignore {
+
+inline unsigned char to_lower(int c) {
+ const static unsigned char table[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
+ 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255,
+ };
+ return table[(unsigned char)(char)c];
+}
+
+inline bool equal(const std::string &a, const std::string &b) {
+ return a.size() == b.size() &&
+ std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
+ return to_lower(ca) == to_lower(cb);
+ });
+}
+
+struct equal_to {
+ bool operator()(const std::string &a, const std::string &b) const {
+ return equal(a, b);
+ }
+};
+
+struct hash {
+ size_t operator()(const std::string &key) const {
+ return hash_core(key.data(), key.size(), 0);
+ }
+
+ size_t hash_core(const char *s, size_t l, size_t h) const {
+ return (l == 0) ? h
+ : hash_core(s + 1, l - 1,
+ // Unsets the 6 high bits of h, therefore no
+ // overflow happens
+ (((std::numeric_limits<size_t>::max)() >> 6) &
+ h * 33) ^
+ static_cast<unsigned char>(to_lower(*s)));
}
};
+} // namespace case_ignore
+
// This is based on
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
NetworkAuthenticationRequired_511 = 511,
};
-using Headers = std::multimap<std::string, std::string, detail::ci>;
+using Headers =
+ std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
+ detail::case_ignore::equal_to>;
using Params = std::multimap<std::string, std::string>;
using Match = std::smatch;
std::ostream os;
private:
- class data_sink_streambuf : public std::streambuf {
+ class data_sink_streambuf final : public std::streambuf {
public:
explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
struct Request {
std::string method;
std::string path;
+ Params params;
Headers headers;
std::string body;
// for server
std::string version;
std::string target;
- Params params;
MultipartFormDataMap files;
Ranges ranges;
Match matches;
std::unordered_map<std::string, std::string> path_params;
+ std::function<bool()> is_connection_closed = []() { return true; };
// for client
ResponseHandler response_handler;
#endif
bool has_header(const std::string &key) const;
- std::string get_header_value(const std::string &key, size_t id = 0) const;
- uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
+ std::string get_header_value(const std::string &key, const char *def = "",
+ size_t id = 0) const;
+ uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
+ size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
ContentProvider content_provider_;
bool is_chunked_content_provider_ = false;
size_t authorization_count_ = 0;
+ std::chrono::time_point<std::chrono::steady_clock> start_time_ =
+ std::chrono::steady_clock::time_point::min();
};
struct Response {
std::string location; // Redirect location
bool has_header(const std::string &key) const;
- std::string get_header_value(const std::string &key, size_t id = 0) const;
- uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
+ std::string get_header_value(const std::string &key, const char *def = "",
+ size_t id = 0) const;
+ uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
+ size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
const std::string &content_type, ContentProviderWithoutLength provider,
ContentProviderResourceReleaser resource_releaser = nullptr);
+ void set_file_content(const std::string &path,
+ const std::string &content_type);
+ void set_file_content(const std::string &path);
+
Response() = default;
Response(const Response &) = default;
Response &operator=(const Response &) = default;
ContentProviderResourceReleaser content_provider_resource_releaser_;
bool is_chunked_content_provider_ = false;
bool content_provider_success_ = false;
+ std::string file_content_path_;
+ std::string file_content_content_type_;
};
class Stream {
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
virtual socket_t socket() const = 0;
- template <typename... Args>
- ssize_t write_format(const char *fmt, const Args &...args);
+ virtual time_t duration() const = 0;
+
ssize_t write(const char *ptr);
ssize_t write(const std::string &s);
};
virtual void on_idle() {}
};
-class ThreadPool : public TaskQueue {
+class ThreadPool final : public TaskQueue {
public:
explicit ThreadPool(size_t n, size_t mqr = 0)
: shutdown_(false), max_queued_requests_(mqr) {
if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
- fn = std::move(pool_.jobs_.front());
+ fn = pool_.jobs_.front();
pool_.jobs_.pop_front();
}
assert(true == static_cast<bool>(fn));
fn();
}
+
+#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ OPENSSL_thread_stop();
+#endif
}
ThreadPool &pool_;
* the resulting capture will be
* {{"capture", "1"}, {"second_capture", "2"}}
*/
-class PathParamsMatcher : public MatcherBase {
+class PathParamsMatcher final : 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
* 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 {
+class RegexMatcher final : public MatcherBase {
public:
RegexMatcher(const std::string &pattern) : regex_(pattern) {}
Server &set_default_file_mimetype(const std::string &mime);
Server &set_file_request_handler(Handler handler);
- Server &set_error_handler(HandlerWithResponse handler);
- Server &set_error_handler(Handler handler);
+ template <class ErrorHandlerFunc>
+ Server &set_error_handler(ErrorHandlerFunc &&handler) {
+ return set_error_handler_core(
+ std::forward<ErrorHandlerFunc>(handler),
+ std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
+ }
+
Server &set_exception_handler(ExceptionHandler handler);
Server &set_pre_routing_handler(HandlerWithResponse handler);
Server &set_post_routing_handler(Handler handler);
Server &set_address_family(int family);
Server &set_tcp_nodelay(bool on);
+ Server &set_ipv6_v6only(bool on);
Server &set_socket_options(SocketOptions socket_options);
Server &set_default_headers(Headers headers);
bool is_running() const;
void wait_until_ready() const;
void stop();
+ void decommission();
std::function<TaskQueue *(void)> new_task_queue;
protected:
- bool process_request(Stream &strm, bool close_connection,
+ bool process_request(Stream &strm, const std::string &remote_addr,
+ int remote_port, const std::string &local_addr,
+ int local_port, bool close_connection,
bool &connection_closed,
const std::function<void(Request &)> &setup_request);
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
- time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
- time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
- time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
- time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
static std::unique_ptr<detail::MatcherBase>
make_matcher(const std::string &pattern);
+ Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
+ Server &set_error_handler_core(Handler handler, std::false_type);
+
socket_t create_server_socket(const std::string &host, int port,
int socket_flags,
SocketOptions socket_options) const;
virtual bool process_and_close_socket(socket_t sock);
std::atomic<bool> is_running_{false};
- std::atomic<bool> done_{false};
+ std::atomic<bool> is_decommisioned{false};
struct MountPointEntry {
std::string mount_point;
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = default_socket_options;
Headers default_headers_;
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
+ SSLServerHostnameVerification,
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
// Request Headers
bool has_request_header(const std::string &key) const;
std::string get_request_header_value(const std::string &key,
+ const char *def = "",
size_t id = 0) const;
uint64_t get_request_header_value_u64(const std::string &key,
- size_t id = 0) const;
+ uint64_t def = 0, size_t id = 0) const;
size_t get_request_header_value_count(const std::string &key) const;
private:
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
size_t content_length, const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Post(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Post(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Post(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Post(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type);
Result Post(const std::string &path, const Params ¶ms);
Result Post(const std::string &path, const Headers &headers,
const Params ¶ms);
+ Result Post(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress);
Result Post(const std::string &path, const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
const std::string &content_type);
Result Put(const std::string &path, const Headers &headers, const char *body,
size_t content_length, const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Put(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Put(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Put(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Put(const std::string &path, size_t content_length,
ContentProvider content_provider, const std::string &content_type);
Result Put(const std::string &path,
Result Put(const std::string &path, const Params ¶ms);
Result Put(const std::string &path, const Headers &headers,
const Params ¶ms);
+ Result Put(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress);
Result Put(const std::string &path, const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
+ Result Patch(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Patch(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Patch(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type);
Result Delete(const std::string &path, const Headers &headers);
Result Delete(const std::string &path, const char *body,
size_t content_length, const std::string &content_type);
+ Result Delete(const std::string &path, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Delete(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Delete(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Delete(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Delete(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Options(const std::string &path);
Result Options(const std::string &path, const Headers &headers);
void set_address_family(int family);
void set_tcp_nodelay(bool on);
+ void set_ipv6_v6only(bool on);
void set_socket_options(SocketOptions socket_options);
void set_connection_timeout(time_t sec, time_t usec = 0);
template <class Rep, class Period>
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+ void set_max_timeout(time_t msec);
+ template <class Rep, class Period>
+ void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
+
void set_basic_auth(const std::string &username, const std::string &password);
void set_bearer_token_auth(const std::string &token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void enable_server_certificate_verification(bool enabled);
+ void enable_server_hostname_verification(bool enabled);
+ void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
#endif
void set_logger(Logger logger);
time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
- time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
- time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
- time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
- time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
+ time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;
std::string basic_auth_username_;
std::string basic_auth_password_;
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = nullptr;
bool compress_ = false;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
bool server_certificate_verification_ = true;
+ bool server_hostname_verification_ = true;
+ std::function<bool(SSL *ssl)> server_certificate_verifier_;
#endif
Logger logger_;
const Headers &headers, const char *body, size_t content_length,
ContentProvider content_provider,
ContentProviderWithoutLength content_provider_without_length,
- const std::string &content_type);
+ const std::string &content_type, Progress progress);
ContentProviderWithoutLength get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) const;
std::string adjust_host_string(const std::string &host) const;
- virtual bool process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback);
+ virtual bool
+ process_socket(const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback);
virtual bool is_ssl() const;
};
const std::string &client_key_path);
Client(Client &&) = default;
+ Client &operator=(Client &&) = default;
~Client();
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
size_t content_length, const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Post(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Post(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Post(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Post(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type);
Result Post(const std::string &path, const Params ¶ms);
Result Post(const std::string &path, const Headers &headers,
const Params ¶ms);
+ Result Post(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress);
Result Post(const std::string &path, const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
const std::string &content_type);
Result Put(const std::string &path, const Headers &headers, const char *body,
size_t content_length, const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Put(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Put(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Put(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Put(const std::string &path, size_t content_length,
ContentProvider content_provider, const std::string &content_type);
Result Put(const std::string &path,
Result Put(const std::string &path, const Params ¶ms);
Result Put(const std::string &path, const Headers &headers,
const Params ¶ms);
+ Result Put(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress);
Result Put(const std::string &path, const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
+ Result Patch(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Patch(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Patch(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Patch(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type);
Result Delete(const std::string &path, const Headers &headers);
Result Delete(const std::string &path, const char *body,
size_t content_length, const std::string &content_type);
+ Result Delete(const std::string &path, const char *body,
+ size_t content_length, const std::string &content_type,
+ Progress progress);
Result Delete(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress);
Result Delete(const std::string &path, const std::string &body,
const std::string &content_type);
+ Result Delete(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress);
Result Delete(const std::string &path, const Headers &headers,
const std::string &body, const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type,
+ Progress progress);
Result Options(const std::string &path);
Result Options(const std::string &path, const Headers &headers);
template <class Rep, class Period>
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+ void set_max_timeout(time_t msec);
+ template <class Rep, class Period>
+ void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
+
void set_basic_auth(const std::string &username, const std::string &password);
void set_bearer_token_auth(const std::string &token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void enable_server_certificate_verification(bool enabled);
+ void enable_server_hostname_verification(bool enabled);
+ void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
#endif
void set_logger(Logger logger);
SSL_CTX *ssl_context() const;
+ void update_certs(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store = nullptr);
+
private:
bool process_and_close_socket(socket_t sock) override;
std::mutex ctx_mutex_;
};
-class SSLClient : public ClientImpl {
+class SSLClient final : public ClientImpl {
public:
explicit SSLClient(const std::string &host);
explicit SSLClient(const std::string &host, int port,
const std::string &client_cert_path,
- const std::string &client_key_path);
+ const std::string &client_key_path,
+ const std::string &private_key_password = std::string());
explicit SSLClient(const std::string &host, int port, X509 *client_cert,
- EVP_PKEY *client_key);
+ EVP_PKEY *client_key,
+ const std::string &private_key_password = std::string());
~SSLClient() override;
void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
- bool process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback) override;
+ bool
+ process_socket(const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback) override;
bool is_ssl() const override;
- bool connect_with_proxy(Socket &sock, Response &res, bool &success,
- Error &error);
+ bool connect_with_proxy(
+ Socket &sock,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ Response &res, bool &success, Error &error);
bool initialize_ssl(Socket &socket, Error &error);
bool load_certs();
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}
+inline bool is_numeric(const std::string &str) {
+ return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
+}
+
inline uint64_t get_header_value_u64(const Headers &headers,
- const std::string &key, size_t id,
- uint64_t def) {
+ const std::string &key, uint64_t def,
+ size_t id, bool &is_invalid_value) {
+ is_invalid_value = false;
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
if (it != rng.second) {
- return std::strtoull(it->second.data(), nullptr, 10);
+ if (is_numeric(it->second)) {
+ return std::strtoull(it->second.data(), nullptr, 10);
+ } else {
+ is_invalid_value = true;
+ }
}
return def;
}
+inline uint64_t get_header_value_u64(const Headers &headers,
+ const std::string &key, uint64_t def,
+ size_t id) {
+ bool dummy = false;
+ return get_header_value_u64(headers, key, def, id, dummy);
+}
+
} // namespace detail
inline uint64_t Request::get_header_value_u64(const std::string &key,
- size_t id) const {
- return detail::get_header_value_u64(headers, key, id, 0);
+ uint64_t def, size_t id) const {
+ return detail::get_header_value_u64(headers, key, def, id);
}
inline uint64_t Response::get_header_value_u64(const std::string &key,
- size_t id) const {
- return detail::get_header_value_u64(headers, key, id, 0);
-}
-
-template <typename... Args>
-inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
- const auto bufsiz = 2048;
- std::array<char, bufsiz> buf{};
-
- auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
- if (sn <= 0) { return sn; }
-
- auto n = static_cast<size_t>(sn);
-
- if (n >= buf.size() - 1) {
- std::vector<char> glowable_buf(buf.size());
-
- while (n >= glowable_buf.size() - 1) {
- glowable_buf.resize(glowable_buf.size() * 2);
- n = static_cast<size_t>(
- snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
- }
- return write(&glowable_buf[0], n);
- } else {
- return write(buf.data(), n);
- }
+ uint64_t def, size_t id) const {
+ return detail::get_header_value_u64(headers, key, def, id);
}
inline void default_socket_options(socket_t sock) {
- int yes = 1;
+ int opt = 1;
#ifdef _WIN32
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
- reinterpret_cast<const char *>(&yes), sizeof(yes));
- setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
- reinterpret_cast<const char *>(&yes), sizeof(yes));
+ reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
#ifdef SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
+ reinterpret_cast<const void *>(&opt), sizeof(opt));
#else
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
+ reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
#endif
}
case Error::SSLConnection: return "SSL connection failed";
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
case Error::SSLServerVerification: return "SSL server verification failed";
+ case Error::SSLServerHostnameVerification:
+ return "SSL server hostname verification failed";
case Error::UnsupportedMultipartBoundaryChars:
return "Unsupported HTTP multipart boundary characters";
case Error::Compression: return "Compression failed";
}
inline uint64_t Result::get_request_header_value_u64(const std::string &key,
+ uint64_t def,
size_t id) const {
- return detail::get_header_value_u64(request_headers_, key, id, 0);
+ return detail::get_header_value_u64(request_headers_, key, def, id);
}
template <class Rep, class Period>
duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
}
+template <class Rep, class Period>
+inline void ClientImpl::set_max_timeout(
+ const std::chrono::duration<Rep, Period> &duration) {
+ auto msec =
+ std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
+ set_max_timeout(msec);
+}
+
template <class Rep, class Period>
inline void Client::set_connection_timeout(
const std::chrono::duration<Rep, Period> &duration) {
cli_->set_write_timeout(duration);
}
+template <class Rep, class Period>
+inline void
+Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
+ cli_->set_max_timeout(duration);
+}
+
/*
* Forward declarations and types that will be part of the .h file if split into
* .h + .cc.
namespace detail {
+#if defined(_WIN32)
+inline std::wstring u8string_to_wstring(const char *s) {
+ std::wstring ws;
+ auto len = static_cast<int>(strlen(s));
+ auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
+ if (wlen > 0) {
+ ws.resize(wlen);
+ wlen = ::MultiByteToWideChar(
+ CP_UTF8, 0, s, len,
+ const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
+ if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
+ }
+ return ws;
+}
+#endif
+
+struct FileStat {
+ FileStat(const std::string &path);
+ bool is_file() const;
+ bool is_dir() const;
+
+private:
+#if defined(_WIN32)
+ struct _stat st_;
+#else
+ struct stat st_;
+#endif
+ int ret_ = -1;
+};
+
std::string encode_query_param(const std::string &value);
std::string decode_url(const std::string &s, bool convert_plus_to_space);
std::string trim_copy(const std::string &s);
+void divide(
+ const char *data, std::size_t size, char d,
+ std::function<void(const char *, std::size_t, const char *, std::size_t)>
+ fn);
+
+void divide(
+ const std::string &str, char d,
+ std::function<void(const char *, std::size_t, const char *, std::size_t)>
+ fn);
+
void split(const char *b, const char *e, char d,
std::function<void(const char *, const char *)> fn);
void split(const char *b, const char *e, char d, size_t m,
std::function<void(const char *, const char *)> fn);
-bool process_client_socket(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec,
- std::function<bool(Stream &)> callback);
-
-socket_t create_client_socket(
- const std::string &host, const std::string &ip, int port,
- int address_family, bool tcp_nodelay, SocketOptions socket_options,
- time_t connection_timeout_sec, time_t connection_timeout_usec,
- time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec, const std::string &intf, Error &error);
+bool process_client_socket(
+ socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &)> callback);
+
+socket_t create_client_socket(const std::string &host, const std::string &ip,
+ int port, int address_family, bool tcp_nodelay,
+ bool ipv6_v6only, SocketOptions socket_options,
+ time_t connection_timeout_sec,
+ time_t connection_timeout_usec,
+ time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec,
+ const std::string &intf, Error &error);
const char *get_header_value(const Headers &headers, const std::string &key,
- size_t id = 0, const char *def = nullptr);
+ const char *def, size_t id);
std::string params_to_query_str(const Params ¶ms);
+void parse_query_text(const char *data, std::size_t size, Params ¶ms);
+
void parse_query_text(const std::string &s, Params ¶ms);
bool parse_multipart_boundary(const std::string &content_type,
EncodingType encoding_type(const Request &req, const Response &res);
-class BufferStream : public Stream {
+class BufferStream final : public Stream {
public:
BufferStream() = default;
~BufferStream() override = default;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
+ time_t duration() const override;
const std::string &get_buffer() const;
Callback callback) = 0;
};
-class nocompressor : public compressor {
+class nocompressor final : public compressor {
public:
~nocompressor() override = default;
};
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-class gzip_compressor : public compressor {
+class gzip_compressor final : public compressor {
public:
gzip_compressor();
~gzip_compressor() override;
z_stream strm_;
};
-class gzip_decompressor : public decompressor {
+class gzip_decompressor final : public decompressor {
public:
gzip_decompressor();
~gzip_decompressor() override;
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
-class brotli_compressor : public compressor {
+class brotli_compressor final : public compressor {
public:
brotli_compressor();
~brotli_compressor();
BrotliEncoderState *state_ = nullptr;
};
-class brotli_decompressor : public decompressor {
+class brotli_decompressor final : public decompressor {
public:
brotli_decompressor();
~brotli_decompressor();
private:
#if defined(_WIN32)
- HANDLE hFile_;
- HANDLE hMapping_;
+ HANDLE hFile_ = NULL;
+ HANDLE hMapping_ = NULL;
#else
- int fd_;
+ int fd_ = -1;
#endif
- size_t size_;
- void *addr_;
+ size_t size_ = 0;
+ void *addr_ = nullptr;
+ bool is_open_empty_file = false;
};
+// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
+namespace fields {
+
+inline bool is_token_char(char c) {
+ return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
+ c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
+ c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
+}
+
+inline bool is_token(const std::string &s) {
+ if (s.empty()) { return false; }
+ for (auto c : s) {
+ if (!is_token_char(c)) { return false; }
+ }
+ return true;
+}
+
+inline bool is_field_name(const std::string &s) { return is_token(s); }
+
+inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
+
+inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
+
+inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
+
+inline bool is_field_content(const std::string &s) {
+ if (s.empty()) { return true; }
+
+ if (s.size() == 1) {
+ return is_field_vchar(s[0]);
+ } else if (s.size() == 2) {
+ return is_field_vchar(s[0]) && is_field_vchar(s[1]);
+ } else {
+ size_t i = 0;
+
+ if (!is_field_vchar(s[i])) { return false; }
+ i++;
+
+ while (i < s.size() - 1) {
+ auto c = s[i++];
+ if (c == ' ' || c == '\t' || is_field_vchar(c)) {
+ } else {
+ return false;
+ }
+ }
+
+ return is_field_vchar(s[i]);
+ }
+}
+
+inline bool is_field_value(const std::string &s) { return is_field_content(s); }
+
+} // namespace fields
+
} // namespace detail
// ----------------------------------------------------------------------------
return out;
}
-inline bool is_file(const std::string &path) {
-#ifdef _WIN32
- return _access_s(path.c_str(), 0) == 0;
-#else
- struct stat st;
- return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
-#endif
-}
-
-inline bool is_dir(const std::string &path) {
- struct stat st;
- return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
-}
-
inline bool is_valid_path(const std::string &path) {
size_t level = 0;
size_t i = 0;
return true;
}
+inline FileStat::FileStat(const std::string &path) {
+#if defined(_WIN32)
+ auto wpath = u8string_to_wstring(path.c_str());
+ ret_ = _wstat(wpath.c_str(), &st_);
+#else
+ ret_ = stat(path.c_str(), &st_);
+#endif
+}
+inline bool FileStat::is_file() const {
+ return ret_ >= 0 && S_ISREG(st_.st_mode);
+}
+inline bool FileStat::is_dir() const {
+ return ret_ >= 0 && S_ISDIR(st_.st_mode);
+}
+
inline std::string encode_query_param(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
return s;
}
+inline void
+divide(const char *data, std::size_t size, char d,
+ std::function<void(const char *, std::size_t, const char *, std::size_t)>
+ fn) {
+ const auto it = std::find(data, data + size, d);
+ const auto found = static_cast<std::size_t>(it != data + size);
+ const auto lhs_data = data;
+ const auto lhs_size = static_cast<std::size_t>(it - data);
+ const auto rhs_data = it + found;
+ const auto rhs_size = size - lhs_size - found;
+
+ fn(lhs_data, lhs_size, rhs_data, rhs_size);
+}
+
+inline void
+divide(const std::string &str, char d,
+ std::function<void(const char *, std::size_t, const char *, std::size_t)>
+ fn) {
+ divide(str.data(), str.size(), d, std::move(fn));
+}
+
inline void split(const char *b, const char *e, char d,
std::function<void(const char *, const char *)> fn) {
return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
fixed_buffer_used_size_ = 0;
glowable_buffer_.clear();
+#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+ char prev_byte = 0;
+#endif
+
for (size_t i = 0;; i++) {
char byte;
auto n = strm_.read(&byte, 1);
append(byte);
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
if (byte == '\n') { break; }
+#else
+ if (prev_byte == '\r' && byte == '\n') { break; }
+ prev_byte = byte;
+#endif
}
return true;
}
}
-inline mmap::mmap(const char *path)
-#if defined(_WIN32)
- : hFile_(NULL), hMapping_(NULL)
-#else
- : fd_(-1)
-#endif
- ,
- size_(0), addr_(nullptr) {
- open(path);
-}
+inline mmap::mmap(const char *path) { open(path); }
inline mmap::~mmap() { close(); }
close();
#if defined(_WIN32)
- hFile_ = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
+ auto wpath = u8string_to_wstring(path);
+ if (wpath.empty()) { return false; }
+
+#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
+ hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
+ OPEN_EXISTING, NULL);
+#else
+ hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+#endif
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
- size_ = ::GetFileSize(hFile_, NULL);
+ LARGE_INTEGER size{};
+ if (!::GetFileSizeEx(hFile_, &size)) { return false; }
+ // If the following line doesn't compile due to QuadPart, update Windows SDK.
+ // See:
+ // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
+ if (static_cast<ULONGLONG>(size.QuadPart) >
+ (std::numeric_limits<decltype(size_)>::max)()) {
+ // `size_t` might be 32-bits, on 32-bits Windows.
+ return false;
+ }
+ size_ = static_cast<size_t>(size.QuadPart);
- hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
+#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
+ hMapping_ =
+ ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
+#else
+ hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
+#endif
+
+ // Special treatment for an empty file...
+ if (hMapping_ == NULL && size_ == 0) {
+ close();
+ is_open_empty_file = true;
+ return true;
+ }
if (hMapping_ == NULL) {
close();
return false;
}
+#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
+ addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
+#else
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
+#endif
+
+ if (addr_ == nullptr) {
+ close();
+ return false;
+ }
#else
fd_ = ::open(path, O_RDONLY);
if (fd_ == -1) { return false; }
size_ = static_cast<size_t>(sb.st_size);
addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
-#endif
- if (addr_ == nullptr) {
+ // Special treatment for an empty file...
+ if (addr_ == MAP_FAILED && size_ == 0) {
close();
+ is_open_empty_file = true;
return false;
}
+#endif
return true;
}
-inline bool mmap::is_open() const { return addr_ != nullptr; }
+inline bool mmap::is_open() const {
+ return is_open_empty_file ? true : addr_ != nullptr;
+}
inline size_t mmap::size() const { return size_; }
inline const char *mmap::data() const {
- return static_cast<const char *>(addr_);
+ return is_open_empty_file ? "" : static_cast<const char *>(addr_);
}
inline void mmap::close() {
::CloseHandle(hFile_);
hFile_ = INVALID_HANDLE_VALUE;
}
+
+ is_open_empty_file = false;
#else
if (addr_ != nullptr) {
munmap(addr_, size_);
ssize_t res = 0;
while (true) {
res = fn();
- if (res < 0 && errno == EINTR) { continue; }
+ if (res < 0 && errno == EINTR) {
+ std::this_thread::sleep_for(std::chrono::microseconds{1});
+ continue;
+ }
break;
}
return res;
});
}
-inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
+template <bool Read>
+inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
- struct pollfd pfd_read;
- pfd_read.fd = sock;
- pfd_read.events = POLLIN;
+ struct pollfd pfd;
+ pfd.fd = sock;
+ pfd.events = (Read ? POLLIN : POLLOUT);
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+ return handle_EINTR([&]() { return poll(&pfd, 1, timeout); });
#else
#ifndef _WIN32
if (sock >= FD_SETSIZE) { return -1; }
#endif
- fd_set fds;
+ fd_set fds, *rfds, *wfds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
+ rfds = (Read ? &fds : nullptr);
+ wfds = (Read ? nullptr : &fds);
timeval tv;
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
return handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
});
#endif
}
-inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
-#ifdef CPPHTTPLIB_USE_POLL
- struct pollfd pfd_read;
- pfd_read.fd = sock;
- pfd_read.events = POLLOUT;
-
- auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
-
- return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
-#else
-#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return -1; }
-#endif
-
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(sock, &fds);
-
- timeval tv;
- tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
+ return select_impl<true>(sock, sec, usec);
+}
- return handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
- });
-#endif
+inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
+ return select_impl<false>(sock, sec, usec);
}
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
}
-class SocketStream : public Stream {
+class SocketStream final : public Stream {
public:
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
- time_t write_timeout_sec, time_t write_timeout_usec);
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec = 0,
+ std::chrono::time_point<std::chrono::steady_clock> start_time =
+ std::chrono::steady_clock::time_point::min());
~SocketStream() override;
bool is_readable() const override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
+ time_t duration() const override;
private:
socket_t sock_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
+ time_t max_timeout_msec_;
+ const std::chrono::time_point<std::chrono::steady_clock> start_time;
std::vector<char> read_buff_;
size_t read_buff_off_ = 0;
};
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-class SSLSocketStream : public Stream {
+class SSLSocketStream final : public Stream {
public:
- SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec);
+ SSLSocketStream(
+ socket_t sock, SSL *ssl, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, time_t max_timeout_msec = 0,
+ std::chrono::time_point<std::chrono::steady_clock> start_time =
+ std::chrono::steady_clock::time_point::min());
~SSLSocketStream() override;
bool is_readable() const override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
+ time_t duration() const override;
private:
socket_t sock_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
+ time_t max_timeout_msec_;
+ const std::chrono::time_point<std::chrono::steady_clock> start_time;
};
#endif
-inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
+inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
+ time_t keep_alive_timeout_sec) {
using namespace std::chrono;
- auto start = steady_clock::now();
+
+ const auto interval_usec =
+ CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
+
+ // Avoid expensive `steady_clock::now()` call for the first time
+ if (select_read(sock, 0, interval_usec) > 0) { return true; }
+
+ const auto start = steady_clock::now() - microseconds{interval_usec};
+ const auto timeout = seconds{keep_alive_timeout_sec};
+
while (true) {
- auto val = select_read(sock, 0, 10000);
+ if (svr_sock == INVALID_SOCKET) {
+ break; // Server socket is closed
+ }
+
+ auto val = select_read(sock, 0, interval_usec);
if (val < 0) {
- return false;
+ break; // Ssocket error
} else if (val == 0) {
- auto current = steady_clock::now();
- auto duration = duration_cast<milliseconds>(current - start);
- auto timeout = keep_alive_timeout_sec * 1000;
- if (duration.count() > timeout) { return false; }
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ if (steady_clock::now() - start > timeout) {
+ break; // Timeout
+ }
} else {
- return true;
+ return true; // Ready for read
}
}
+
+ return false;
}
template <typename T>
assert(keep_alive_max_count > 0);
auto ret = false;
auto count = keep_alive_max_count;
- while (svr_sock != INVALID_SOCKET && count > 0 &&
- keep_alive(sock, keep_alive_timeout_sec)) {
+ while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
auto close_connection = count == 1;
auto connection_closed = false;
ret = callback(close_connection, connection_closed);
});
}
-inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec,
- time_t write_timeout_sec,
- time_t write_timeout_usec,
- std::function<bool(Stream &)> callback) {
+inline bool process_client_socket(
+ socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &)> callback) {
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
- write_timeout_sec, write_timeout_usec);
+ write_timeout_sec, write_timeout_usec, max_timeout_msec,
+ start_time);
return callback(strm);
}
#endif
}
+inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
+ if (s.size() > 1 && s[0] == '\0') {
+ auto ret = s;
+ ret[0] = '@';
+ return ret;
+ }
+ return s;
+}
+
+inline std::string
+unescape_abstract_namespace_unix_domain(const std::string &s) {
+ if (s.size() > 1 && s[0] == '@') {
+ auto ret = s;
+ ret[0] = '\0';
+ return ret;
+ }
+ return s;
+}
+
template <typename BindOrConnect>
socket_t create_socket(const std::string &host, const std::string &ip, int port,
int address_family, int socket_flags, bool tcp_nodelay,
- SocketOptions socket_options,
+ bool ipv6_v6only, SocketOptions socket_options,
BindOrConnect bind_or_connect) {
// Get address info
const char *node = nullptr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = 0;
+ hints.ai_protocol = IPPROTO_IP;
if (!ip.empty()) {
node = ip.c_str();
const auto addrlen = host.length();
if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
+#ifdef SOCK_CLOEXEC
+ auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
+ hints.ai_protocol);
+#else
auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
+#endif
+
if (sock != INVALID_SOCKET) {
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
- std::copy(host.begin(), host.end(), addr.sun_path);
+
+ auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
+ std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
hints.ai_addrlen = static_cast<socklen_t>(
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
+#ifndef SOCK_CLOEXEC
fcntl(sock, F_SETFD, FD_CLOEXEC);
+#endif
+
if (socket_options) { socket_options(sock); }
- if (!bind_or_connect(sock, hints)) {
+ bool dummy;
+ if (!bind_or_connect(sock, hints, dummy)) {
close_socket(sock);
sock = INVALID_SOCKET;
}
#endif
return INVALID_SOCKET;
}
+ auto se = detail::scope_exit([&] { freeaddrinfo(result); });
for (auto rp = result; rp; rp = rp->ai_next) {
// Create a socket
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
}
#else
+
+#ifdef SOCK_CLOEXEC
+ auto sock =
+ socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
+#else
auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+#endif
+
#endif
if (sock == INVALID_SOCKET) { continue; }
-#ifndef _WIN32
+#if !defined _WIN32 && !defined SOCK_CLOEXEC
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
close_socket(sock);
continue;
#endif
if (tcp_nodelay) {
- auto yes = 1;
+ auto opt = 1;
#ifdef _WIN32
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const char *>(&yes), sizeof(yes));
+ reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
+ reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
- if (socket_options) { socket_options(sock); }
-
if (rp->ai_family == AF_INET6) {
- auto no = 0;
+ auto opt = ipv6_v6only ? 1 : 0;
#ifdef _WIN32
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const char *>(&no), sizeof(no));
+ reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const void *>(&no), sizeof(no));
+ reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
+ if (socket_options) { socket_options(sock); }
+
// bind or connect
- if (bind_or_connect(sock, *rp)) {
- freeaddrinfo(result);
- return sock;
- }
+ auto quit = false;
+ if (bind_or_connect(sock, *rp, quit)) { return sock; }
close_socket(sock);
+
+ if (quit) { break; }
}
- freeaddrinfo(result);
return INVALID_SOCKET;
}
hints.ai_protocol = 0;
if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; }
+ auto se = detail::scope_exit([&] { freeaddrinfo(result); });
auto ret = false;
for (auto rp = result; rp; rp = rp->ai_next) {
}
}
- freeaddrinfo(result);
return ret;
}
inline std::string if2ip(int address_family, const std::string &ifn) {
struct ifaddrs *ifap;
getifaddrs(&ifap);
+ auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
+
std::string addr_candidate;
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr && ifn == ifa->ifa_name &&
auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
char buf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
- freeifaddrs(ifap);
return std::string(buf, INET_ADDRSTRLEN);
}
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
} else {
- freeifaddrs(ifap);
return std::string(buf, INET6_ADDRSTRLEN);
}
}
}
}
}
- freeifaddrs(ifap);
return addr_candidate;
}
#endif
inline socket_t create_client_socket(
const std::string &host, const std::string &ip, int port,
- int address_family, bool tcp_nodelay, SocketOptions socket_options,
- time_t connection_timeout_sec, time_t connection_timeout_usec,
- time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+ int address_family, bool tcp_nodelay, bool ipv6_v6only,
+ SocketOptions socket_options, time_t connection_timeout_sec,
+ time_t connection_timeout_usec, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, const std::string &intf, Error &error) {
auto sock = create_socket(
- host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
- [&](socket_t sock2, struct addrinfo &ai) -> bool {
+ host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
+ std::move(socket_options),
+ [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
if (!intf.empty()) {
#ifdef USE_IF2IP
auto ip_from_if = if2ip(address_family, intf);
}
error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
connection_timeout_usec);
- if (error != Error::Success) { return false; }
+ if (error != Error::Success) {
+ if (error == Error::ConnectionTimeout) { quit = true; }
+ return false;
+ }
}
set_nonblocking(sock2, false);
namespace udl {
-inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
+inline constexpr unsigned int operator""_t(const char *s, size_t l) {
return str2tag_core(s, l, 0);
}
case "application/protobuf"_t:
case "application/xhtml+xml"_t: return true;
- default:
- return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
+ case "text/event-stream"_t: return false;
+
+ default: return !content_type.rfind("text/", 0);
}
}
}
inline const char *get_header_value(const Headers &headers,
- const std::string &key, size_t id,
- const char *def) {
+ const std::string &key, const char *def,
+ size_t id) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
return def;
}
-inline bool compare_case_ignore(const std::string &a, const std::string &b) {
- if (a.size() != b.size()) { return false; }
- for (size_t i = 0; i < b.size(); i++) {
- if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
- }
- return true;
-}
-
template <typename T>
inline bool parse_header(const char *beg, const char *end, T fn) {
// Skip trailing spaces and tabs.
p++;
}
- if (p < end) {
+ if (p <= end) {
auto key_len = key_end - beg;
if (!key_len) { return false; }
auto key = std::string(beg, key_end);
- auto val = compare_case_ignore(key, "Location")
- ? std::string(p, end)
- : decode_url(std::string(p, end), false);
- fn(std::move(key), std::move(val));
+ // auto val = (case_ignore::equal(key, "Location") ||
+ // case_ignore::equal(key, "Referer"))
+ // ? std::string(p, end)
+ // : decode_url(std::string(p, end), false);
+ auto val = std::string(p, end);
+
+ if (!detail::fields::is_field_value(val)) { return false; }
+
+ if (case_ignore::equal(key, "Location") ||
+ case_ignore::equal(key, "Referer")) {
+ fn(key, val);
+ } else {
+ fn(key, decode_url(val, false));
+ }
+
return true;
}
if (line_reader.end_with_crlf()) {
// Blank line indicates end of headers.
if (line_reader.size() == 2) { break; }
-#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
} else {
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
// Blank line indicates end of headers.
if (line_reader.size() == 1) { break; }
line_terminator_len = 1;
- }
#else
- } else {
continue; // Skip invalid line.
- }
#endif
+ }
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
// Exclude line terminator
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
- parse_header(line_reader.ptr(), end,
- [&](std::string &&key, std::string &&val) {
- headers.emplace(std::move(key), std::move(val));
- });
+ if (!parse_header(line_reader.ptr(), end,
+ [&](const std::string &key, const std::string &val) {
+ headers.emplace(key, val);
+ })) {
+ return false;
+ }
}
return true;
uint64_t r = 0;
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
- if (n <= 0) { return true; }
+ if (n <= 0) { return false; }
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
r += static_cast<uint64_t>(n);
assert(chunk_len == 0);
- // Trailer
- if (!line_reader.getline()) { return false; }
+ // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked
+ // transfer coding is complete when a chunk with a chunk-size of zero is
+ // received, possibly followed by a trailer section, and finally terminated by
+ // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
+ //
+ // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
+ // does't care for the existence of the final CRLF. In other words, it seems
+ // to be ok whether the final CRLF exists or not in the chunked data.
+ // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
+ //
+ // According to the reference code in RFC 9112, cpp-htpplib now allows
+ // chuncked transfer coding data without the final CRLF.
+ if (!line_reader.getline()) { return true; }
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
parse_header(line_reader.ptr(), end,
- [&](std::string &&key, std::string &&val) {
- x.headers.emplace(std::move(key), std::move(val));
+ [&](const std::string &key, const std::string &val) {
+ x.headers.emplace(key, val);
});
if (!line_reader.getline()) { return false; }
}
inline bool is_chunked_transfer_encoding(const Headers &headers) {
- return compare_case_ignore(
- get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
+ return case_ignore::equal(
+ get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
}
template <typename T, typename U>
} else if (!has_header(x.headers, "Content-Length")) {
ret = read_content_without_length(strm, out);
} else {
- auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0);
- if (len > payload_max_length) {
+ auto is_invalid_value = false;
+ auto len = get_header_value_u64(
+ x.headers, "Content-Length",
+ (std::numeric_limits<uint64_t>::max)(), 0, is_invalid_value);
+
+ if (is_invalid_value) {
+ ret = false;
+ } else if (len > payload_max_length) {
exceed_payload_max_length = true;
skip_content_with_length(strm, len);
ret = false;
}
return ret;
});
-} // namespace detail
+}
+
+inline ssize_t write_request_line(Stream &strm, const std::string &method,
+ const std::string &path) {
+ std::string s = method;
+ s += " ";
+ s += path;
+ s += " HTTP/1.1\r\n";
+ return strm.write(s.data(), s.size());
+}
+
+inline ssize_t write_response_line(Stream &strm, int status) {
+ std::string s = "HTTP/1.1 ";
+ s += std::to_string(status);
+ s += " ";
+ s += httplib::status_message(status);
+ s += "\r\n";
+ return strm.write(s.data(), s.size());
+}
inline ssize_t write_headers(Stream &strm, const Headers &headers) {
ssize_t write_len = 0;
for (const auto &x : headers) {
- auto len =
- strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
+ std::string s;
+ s = x.first;
+ s += ": ";
+ s += x.second;
+ s += "\r\n";
+
+ auto len = strm.write(s.data(), s.size());
if (len < 0) { return len; }
write_len += len;
}
return query;
}
-inline void parse_query_text(const std::string &s, Params ¶ms) {
+inline void parse_query_text(const char *data, std::size_t size,
+ Params ¶ms) {
std::set<std::string> cache;
- split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
+ split(data, data + size, '&', [&](const char *b, const char *e) {
std::string kv(b, e);
if (cache.find(kv) != cache.end()) { return; }
- cache.insert(kv);
+ cache.insert(std::move(kv));
std::string key;
std::string val;
- split(b, e, '=', [&](const char *b2, const char *e2) {
- if (key.empty()) {
- key.assign(b2, e2);
- } else {
- val.assign(b2, e2);
- }
- });
+ divide(b, static_cast<std::size_t>(e - b), '=',
+ [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
+ std::size_t rhs_size) {
+ key.assign(lhs_data, lhs_size);
+ val.assign(rhs_data, rhs_size);
+ });
if (!key.empty()) {
params.emplace(decode_url(key, true), decode_url(val, true));
});
}
+inline void parse_query_text(const std::string &s, Params ¶ms) {
+ parse_query_text(s.data(), s.size(), params);
+}
+
inline bool parse_multipart_boundary(const std::string &content_type,
std::string &boundary) {
auto boundary_keyword = "boundary=";
#else
inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
#endif
- static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
- std::smatch m;
- if (std::regex_match(s, m, re_first_range)) {
- auto pos = static_cast<size_t>(m.position(1));
- auto len = static_cast<size_t>(m.length(1));
+ auto is_valid = [](const std::string &str) {
+ return std::all_of(str.cbegin(), str.cend(),
+ [](unsigned char c) { return std::isdigit(c); });
+ };
+
+ if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
+ const auto pos = static_cast<size_t>(6);
+ const auto len = static_cast<size_t>(s.size() - 6);
auto all_valid_ranges = true;
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
if (!all_valid_ranges) { return; }
- static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
- std::cmatch cm;
- if (std::regex_match(b, e, cm, re_another_range)) {
- ssize_t first = -1;
- if (!cm.str(1).empty()) {
- first = static_cast<ssize_t>(std::stoll(cm.str(1)));
- }
- ssize_t last = -1;
- if (!cm.str(2).empty()) {
- last = static_cast<ssize_t>(std::stoll(cm.str(2)));
- }
+ const auto it = std::find(b, e, '-');
+ if (it == e) {
+ all_valid_ranges = false;
+ return;
+ }
- if (first != -1 && last != -1 && first > last) {
- all_valid_ranges = false;
- return;
- }
- ranges.emplace_back(std::make_pair(first, last));
+ const auto lhs = std::string(b, it);
+ const auto rhs = std::string(it + 1, e);
+ if (!is_valid(lhs) || !is_valid(rhs)) {
+ all_valid_ranges = false;
+ return;
+ }
+
+ const auto first =
+ static_cast<ssize_t>(lhs.empty() ? -1 : std::stoll(lhs));
+ const auto last =
+ static_cast<ssize_t>(rhs.empty() ? -1 : std::stoll(rhs));
+ if ((first == -1 && last == -1) ||
+ (first != -1 && last != -1 && first > last)) {
+ all_valid_ranges = false;
+ return;
}
+
+ ranges.emplace_back(first, last);
});
- return all_valid_ranges;
+ return all_valid_ranges && !ranges.empty();
}
return false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
const auto header = buf_head(pos);
if (!parse_header(header.data(), header.data() + header.size(),
- [&](std::string &&, std::string &&) {})) {
+ [&](const std::string &, const std::string &) {})) {
is_valid_ = false;
return false;
}
const std::string &b) const {
if (a.size() < b.size()) { return false; }
for (size_t i = 0; i < b.size(); i++) {
- if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+ if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) {
+ return false;
+ }
}
return true;
}
size_t buf_epos_ = 0;
};
-inline std::string to_lower(const char *beg, const char *end) {
- std::string out;
- auto it = beg;
- while (it != end) {
- out += static_cast<char>(::tolower(*it));
- it++;
- }
- return out;
-}
-
inline std::string random_string(size_t length) {
static const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
last_pos = contant_len - 1;
}
- if (last_pos == -1) { last_pos = contant_len - 1; }
+ // NOTE: RFC-9110 '14.1.2. Byte Ranges':
+ // A client can limit the number of bytes requested without knowing the
+ // size of the selected representation. If the last-pos value is absent,
+ // or if the value is greater than or equal to the current length of the
+ // representation data, the byte range is interpreted as the remainder of
+ // the representation (i.e., the server replaces the value of last-pos
+ // with a value that is one less than the current length of the selected
+ // representation).
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
+ if (last_pos == -1 || last_pos >= contant_len) {
+ last_pos = contant_len - 1;
+ }
// Range must be within content length
if (!(0 <= first_pos && first_pos <= last_pos &&
assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
assert(r.first <= r.second &&
r.second < static_cast<ssize_t>(content_length));
-
+ (void)(content_length);
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
}
inline bool expect_content(const Request &req) {
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
- req.method == "PRI" || req.method == "DELETE") {
+ req.method == "DELETE") {
return true;
}
- // TODO: check if Content-Length is set
+ if (req.has_header("Content-Length") &&
+ req.get_header_value_u64("Content-Length") > 0) {
+ return true;
+ }
+ if (is_chunked_transfer_encoding(req.headers)) { return true; }
return false;
}
inline std::string SHA_512(const std::string &s) {
return message_digest(s, EVP_sha512());
}
-#endif
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline std::pair<std::string, std::string> make_digest_authentication_header(
+ const Request &req, const std::map<std::string, std::string> &auth,
+ size_t cnonce_count, const std::string &cnonce, const std::string &username,
+ const std::string &password, bool is_proxy = false) {
+ std::string nc;
+ {
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
+ nc = ss.str();
+ }
+
+ std::string qop;
+ if (auth.find("qop") != auth.end()) {
+ qop = auth.at("qop");
+ if (qop.find("auth-int") != std::string::npos) {
+ qop = "auth-int";
+ } else if (qop.find("auth") != std::string::npos) {
+ qop = "auth";
+ } else {
+ qop.clear();
+ }
+ }
+
+ std::string algo = "MD5";
+ if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
+
+ std::string response;
+ {
+ auto H = algo == "SHA-256" ? detail::SHA_256
+ : algo == "SHA-512" ? detail::SHA_512
+ : detail::MD5;
+
+ auto A1 = username + ":" + auth.at("realm") + ":" + password;
+
+ auto A2 = req.method + ":" + req.path;
+ if (qop == "auth-int") { A2 += ":" + H(req.body); }
+
+ if (qop.empty()) {
+ response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
+ } else {
+ response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
+ ":" + qop + ":" + H(A2));
+ }
+ }
+
+ auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
+
+ auto field = "Digest username=\"" + username + "\", realm=\"" +
+ auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
+ "\", uri=\"" + req.path + "\", algorithm=" + algo +
+ (qop.empty() ? ", response=\""
+ : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
+ cnonce + "\", response=\"") +
+ response + "\"" +
+ (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
+
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, field);
+}
+
+inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
+ detail::set_nonblocking(sock, true);
+ auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
+
+ char buf[1];
+ return !SSL_peek(ssl, buf, 1) &&
+ SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
+}
+
#ifdef _WIN32
// NOTE: This code came up with the following stackoverflow post:
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
static WSInit wsinit_;
#endif
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline std::pair<std::string, std::string> make_digest_authentication_header(
- const Request &req, const std::map<std::string, std::string> &auth,
- size_t cnonce_count, const std::string &cnonce, const std::string &username,
- const std::string &password, bool is_proxy = false) {
- std::string nc;
- {
- std::stringstream ss;
- ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
- nc = ss.str();
- }
-
- std::string qop;
- if (auth.find("qop") != auth.end()) {
- qop = auth.at("qop");
- if (qop.find("auth-int") != std::string::npos) {
- qop = "auth-int";
- } else if (qop.find("auth") != std::string::npos) {
- qop = "auth";
- } else {
- qop.clear();
- }
- }
-
- std::string algo = "MD5";
- if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
-
- std::string response;
- {
- auto H = algo == "SHA-256" ? detail::SHA_256
- : algo == "SHA-512" ? detail::SHA_512
- : detail::MD5;
-
- auto A1 = username + ":" + auth.at("realm") + ":" + password;
-
- auto A2 = req.method + ":" + req.path;
- if (qop == "auth-int") { A2 += ":" + H(req.body); }
-
- if (qop.empty()) {
- response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
- } else {
- response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
- ":" + qop + ":" + H(A2));
- }
- }
-
- auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
-
- auto field = "Digest username=\"" + username + "\", realm=\"" +
- auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
- "\", uri=\"" + req.path + "\", algorithm=" + algo +
- (qop.empty() ? ", response=\""
- : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
- cnonce + "\", response=\"") +
- response + "\"" +
- (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
-
- auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
- return std::make_pair(key, field);
-}
-#endif
-
inline bool parse_www_authenticate(const Response &res,
std::map<std::string, std::string> &auth,
bool is_proxy) {
#endif
return;
}
+ auto se = detail::scope_exit([&] { freeaddrinfo(result); });
for (auto rp = result; rp; rp = rp->ai_next) {
const auto &addr =
addrs.push_back(ip);
}
}
-
- freeaddrinfo(result);
}
inline std::string append_query_params(const std::string &path,
}
inline std::string Request::get_header_value(const std::string &key,
- size_t id) const {
- return detail::get_header_value(headers, key, id, "");
+ const char *def, size_t id) const {
+ return detail::get_header_value(headers, key, def, id);
}
inline size_t Request::get_header_value_count(const std::string &key) const {
inline void Request::set_header(const std::string &key,
const std::string &val) {
- if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ if (detail::fields::is_field_name(key) &&
+ detail::fields::is_field_value(val)) {
headers.emplace(key, val);
}
}
}
inline std::string Response::get_header_value(const std::string &key,
+ const char *def,
size_t id) const {
- return detail::get_header_value(headers, key, id, "");
+ return detail::get_header_value(headers, key, def, id);
}
inline size_t Response::get_header_value_count(const std::string &key) const {
inline void Response::set_header(const std::string &key,
const std::string &val) {
- if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ if (detail::fields::is_field_name(key) &&
+ detail::fields::is_field_value(val)) {
headers.emplace(key, val);
}
}
inline void Response::set_redirect(const std::string &url, int stat) {
- if (!detail::has_crlf(url)) {
+ if (detail::fields::is_field_value(url)) {
set_header("Location", url);
if (300 <= stat && stat < 400) {
this->status = stat;
is_chunked_content_provider_ = true;
}
+inline void Response::set_file_content(const std::string &path,
+ const std::string &content_type) {
+ file_content_path_ = path;
+ file_content_content_type_ = content_type;
+}
+
+inline void Response::set_file_content(const std::string &path) {
+ file_content_path_ = path;
+}
+
// Result implementation
inline bool Result::has_request_header(const std::string &key) const {
return request_headers_.find(key) != request_headers_.end();
}
inline std::string Result::get_request_header_value(const std::string &key,
+ const char *def,
size_t id) const {
- return detail::get_header_value(request_headers_, key, id, "");
+ return detail::get_header_value(request_headers_, key, def, id);
}
inline size_t
namespace detail {
+inline void calc_actual_timeout(time_t max_timeout_msec,
+ time_t duration_msec, time_t timeout_sec,
+ time_t timeout_usec, time_t &actual_timeout_sec,
+ time_t &actual_timeout_usec) {
+ auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
+
+ auto actual_timeout_msec =
+ std::min(max_timeout_msec - duration_msec, timeout_msec);
+
+ actual_timeout_sec = actual_timeout_msec / 1000;
+ actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
+}
+
// Socket stream implementation
-inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec,
- time_t write_timeout_sec,
- time_t write_timeout_usec)
+inline SocketStream::SocketStream(
+ socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time)
: sock_(sock), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
- write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
+ write_timeout_usec_(write_timeout_usec),
+ max_timeout_msec_(max_timeout_msec), start_time(start_time),
+ read_buff_(read_buff_size_, 0) {}
inline SocketStream::~SocketStream() = default;
inline bool SocketStream::is_readable() const {
- return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ if (max_timeout_msec_ <= 0) {
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ }
+
+ time_t read_timeout_sec;
+ time_t read_timeout_usec;
+ calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
+ read_timeout_usec_, read_timeout_sec, read_timeout_usec);
+
+ return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
}
inline bool SocketStream::is_writable() const {
inline socket_t SocketStream::socket() const { return sock_; }
+inline time_t SocketStream::duration() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time)
+ .count();
+}
+
// Buffer stream implementation
inline bool BufferStream::is_readable() const { return true; }
inline socket_t BufferStream::socket() const { return 0; }
+inline time_t BufferStream::duration() const { return 0; }
+
inline const std::string &BufferStream::get_buffer() const { return buffer; }
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
+ static constexpr char marker[] = "/:";
+
// One past the last ending position of a path param substring
std::size_t last_param_end = 0;
#endif
while (true) {
- const auto marker_pos = pattern.find(marker, last_param_end);
+ const auto marker_pos = pattern.find(
+ marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
if (marker_pos == std::string::npos) { break; }
static_fragments_.push_back(
- pattern.substr(last_param_end, marker_pos - last_param_end));
+ pattern.substr(last_param_end, marker_pos - last_param_end + 1));
- const auto param_name_start = marker_pos + 1;
+ const auto param_name_start = marker_pos + 2;
auto sep_pos = pattern.find(separator, param_name_start);
if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
request.path_params.emplace(
param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
- // Mark everythin up to '/' as matched
+ // Mark everything up to '/' as matched
starting_pos = sep_pos + 1;
}
// Returns false if the path is longer than the pattern
inline bool Server::set_mount_point(const std::string &mount_point,
const std::string &dir, Headers headers) {
- if (detail::is_dir(dir)) {
+ detail::FileStat stat(dir);
+ if (stat.is_dir()) {
std::string mnt = !mount_point.empty() ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
base_dirs_.push_back({mnt, dir, std::move(headers)});
return *this;
}
-inline Server &Server::set_error_handler(HandlerWithResponse handler) {
+inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
+ std::true_type) {
error_handler_ = std::move(handler);
return *this;
}
-inline Server &Server::set_error_handler(Handler handler) {
+inline Server &Server::set_error_handler_core(Handler handler,
+ std::false_type) {
error_handler_ = [handler](const Request &req, Response &res) {
handler(req, res);
return HandlerResponse::Handled;
return *this;
}
+inline Server &Server::set_ipv6_v6only(bool on) {
+ ipv6_v6only_ = on;
+ return *this;
+}
+
inline Server &Server::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
return *this;
inline bool Server::bind_to_port(const std::string &host, int port,
int socket_flags) {
- return bind_internal(host, port, socket_flags) >= 0;
+ auto ret = bind_internal(host, port, socket_flags);
+ if (ret == -1) { is_decommisioned = true; }
+ return ret >= 0;
}
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
- return bind_internal(host, 0, socket_flags);
+ auto ret = bind_internal(host, 0, socket_flags);
+ if (ret == -1) { is_decommisioned = true; }
+ return ret;
}
-inline bool Server::listen_after_bind() {
- auto se = detail::scope_exit([&]() { done_ = true; });
- return listen_internal();
-}
+inline bool Server::listen_after_bind() { return listen_internal(); }
inline bool Server::listen(const std::string &host, int port,
int socket_flags) {
- auto se = detail::scope_exit([&]() { done_ = true; });
return bind_to_port(host, port, socket_flags) && listen_internal();
}
inline bool Server::is_running() const { return is_running_; }
inline void Server::wait_until_ready() const {
- while (!is_running() && !done_) {
+ while (!is_running_ && !is_decommisioned) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
}
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
+ is_decommisioned = false;
}
+inline void Server::decommission() { is_decommisioned = true; }
+
inline bool Server::parse_request_line(const char *s, Request &req) const {
auto len = strlen(s);
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
}
}
- size_t count = 0;
-
- detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
- 2, [&](const char *b, const char *e) {
- switch (count) {
- case 0:
- req.path = detail::decode_url(std::string(b, e), false);
- break;
- case 1: {
- if (e - b > 0) {
- detail::parse_query_text(std::string(b, e), req.params);
- }
- break;
- }
- default: break;
- }
- count++;
- });
-
- if (count > 2) { return false; }
+ detail::divide(req.target, '?',
+ [&](const char *lhs_data, std::size_t lhs_size,
+ const char *rhs_data, std::size_t rhs_size) {
+ req.path = detail::decode_url(
+ std::string(lhs_data, lhs_size), false);
+ detail::parse_query_text(rhs_data, rhs_size, req.params);
+ });
}
return true;
if (close_connection || req.get_header_value("Connection") == "close") {
res.set_header("Connection", "close");
} else {
- std::stringstream ss;
- ss << "timeout=" << keep_alive_timeout_sec_
- << ", max=" << keep_alive_max_count_;
- res.set_header("Keep-Alive", ss.str());
+ std::string s = "timeout=";
+ s += std::to_string(keep_alive_timeout_sec_);
+ s += ", max=";
+ s += std::to_string(keep_alive_max_count_);
+ res.set_header("Keep-Alive", s);
}
- if (!res.has_header("Content-Type") &&
- (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
+ if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
+ !res.has_header("Content-Type")) {
res.set_header("Content-Type", "text/plain");
}
- if (!res.has_header("Content-Length") && res.body.empty() &&
- !res.content_length_ && !res.content_provider_) {
+ if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
+ !res.has_header("Content-Length")) {
res.set_header("Content-Length", "0");
}
- if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
+ if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
res.set_header("Accept-Ranges", "bytes");
}
if (post_routing_handler_) { post_routing_handler_(req, res); }
// Response line and headers
- {
- detail::BufferStream bstrm;
-
- if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
- status_message(res.status))) {
- return false;
- }
-
+ {
+ detail::BufferStream bstrm;
+ if (!detail::write_response_line(bstrm, res.status)) { return false; }
if (!header_writer_(bstrm, res.headers)) { return false; }
// Flush buffer
auto path = entry.base_dir + sub_path;
if (path.back() == '/') { path += "index.html"; }
- if (detail::is_file(path)) {
+ detail::FileStat stat(path);
+
+ if (stat.is_dir()) {
+ res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
+ return true;
+ }
+
+ if (stat.is_file()) {
for (const auto &kv : entry.headers) {
res.set_header(kv.first, kv.second);
}
SocketOptions socket_options) const {
return detail::create_socket(
host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
- std::move(socket_options),
- [](socket_t sock, struct addrinfo &ai) -> bool {
+ ipv6_v6only_, std::move(socket_options),
+ [](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
}
inline int Server::bind_internal(const std::string &host, int port,
int socket_flags) {
+ if (is_decommisioned) { return -1; }
+
if (!is_valid()) { return -1; }
svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
}
inline bool Server::listen_internal() {
+ if (is_decommisioned) { return false; }
+
auto ret = true;
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
#ifndef _WIN32
}
#endif
+
+#if defined _WIN32
+ // sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT,
+ // OVERLAPPED
+ socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
+#elif defined SOCK_CLOEXEC
+ socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
+#else
socket_t sock = accept(svr_sock_, nullptr, nullptr);
+#endif
if (sock == INVALID_SOCKET) {
if (errno == EMFILE) {
// The per-process limit of open file descriptors has been reached.
// Try to accept new connections after a short sleep.
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::microseconds{1});
continue;
} else if (errno == EINTR || errno == EAGAIN) {
continue;
task_queue->shutdown();
}
+ is_decommisioned = !ret;
return ret;
}
inline void Server::apply_ranges(const Request &req, Response &res,
std::string &content_type,
std::string &boundary) const {
- if (req.ranges.size() > 1) {
+ if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
auto it = res.headers.find("Content-Type");
if (it != res.headers.end()) {
content_type = it->second;
if (res.body.empty()) {
if (res.content_length_ > 0) {
size_t length = 0;
- if (req.ranges.empty()) {
+ if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
length = res.content_length_;
} else if (req.ranges.size() == 1) {
auto offset_and_length = detail::get_range_offset_and_length(
}
}
} else {
- if (req.ranges.empty()) {
+ if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
;
} else if (req.ranges.size() == 1) {
auto offset_and_length =
}
inline bool
-Server::process_request(Stream &strm, bool close_connection,
+Server::process_request(Stream &strm, const std::string &remote_addr,
+ int remote_port, const std::string &local_addr,
+ int local_port, bool close_connection,
bool &connection_closed,
const std::function<void(Request &)> &setup_request) {
std::array<char, 2048> buf{};
#endif
#endif
- // Check if the request URI doesn't exceed the limit
- if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
- Headers dummy;
- detail::read_headers(strm, dummy);
- res.status = StatusCode::UriTooLong_414;
- return write_response(strm, close_connection, req, res);
- }
-
// Request line and headers
if (!parse_request_line(line_reader.ptr(), req) ||
!detail::read_headers(strm, req.headers)) {
return write_response(strm, close_connection, req, res);
}
+ // Check if the request URI doesn't exceed the limit
+ if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+ Headers dummy;
+ detail::read_headers(strm, dummy);
+ res.status = StatusCode::UriTooLong_414;
+ return write_response(strm, close_connection, req, res);
+ }
+
if (req.get_header_value("Connection") == "close") {
connection_closed = true;
}
connection_closed = true;
}
- strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
+ req.remote_addr = remote_addr;
+ req.remote_port = remote_port;
req.set_header("REMOTE_ADDR", req.remote_addr);
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
- strm.get_local_ip_and_port(req.local_addr, req.local_port);
+ req.local_addr = local_addr;
+ req.local_port = local_port;
req.set_header("LOCAL_ADDR", req.local_addr);
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
switch (status) {
case StatusCode::Continue_100:
case StatusCode::ExpectationFailed_417:
- strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
- status_message(status));
+ detail::write_response_line(strm, status);
+ strm.write("\r\n");
break;
- default: return write_response(strm, close_connection, req, res);
+ default:
+ connection_closed = true;
+ return write_response(strm, true, req, res);
}
}
+ // Setup `is_connection_closed` method
+ req.is_connection_closed = [&]() {
+ return !detail::is_socket_alive(strm.socket());
+ };
+
// Routing
auto routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
: StatusCode::PartialContent_206;
}
+ // Serve file content by using a content provider
+ if (!res.file_content_path_.empty()) {
+ const auto &path = res.file_content_path_;
+ auto mm = std::make_shared<detail::mmap>(path.c_str());
+ if (!mm->is_open()) {
+ res.body.clear();
+ res.content_length_ = 0;
+ res.content_provider_ = nullptr;
+ res.status = StatusCode::NotFound_404;
+ return write_response(strm, close_connection, req, res);
+ }
+
+ auto content_type = res.file_content_content_type_;
+ if (content_type.empty()) {
+ content_type = detail::find_content_type(
+ path, file_extension_and_mimetype_map_, default_file_mimetype_);
+ }
+
+ res.set_content_provider(
+ mm->size(), content_type,
+ [mm](size_t offset, size_t length, DataSink &sink) -> bool {
+ sink.write(mm->data() + offset, length);
+ return true;
+ });
+ }
+
if (detail::range_error(req, res)) {
res.body.clear();
res.content_length_ = 0;
inline bool Server::is_valid() const { return true; }
inline bool Server::process_and_close_socket(socket_t sock) {
+ std::string remote_addr;
+ int remote_port = 0;
+ detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
+
+ std::string local_addr;
+ int local_port = 0;
+ detail::get_local_ip_and_port(sock, local_addr, local_port);
+
auto ret = detail::process_server_socket(
svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_,
- [this](Stream &strm, bool close_connection, bool &connection_closed) {
- return process_request(strm, close_connection, connection_closed,
+ [&](Stream &strm, bool close_connection, bool &connection_closed) {
+ return process_request(strm, remote_addr, remote_port, local_addr,
+ local_port, close_connection, connection_closed,
nullptr);
});
inline ClientImpl::ClientImpl(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
- : host_(host), port_(port),
- host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
+ : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
+ host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)),
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
inline ClientImpl::~ClientImpl() {
read_timeout_usec_ = rhs.read_timeout_usec_;
write_timeout_sec_ = rhs.write_timeout_sec_;
write_timeout_usec_ = rhs.write_timeout_usec_;
+ max_timeout_msec_ = rhs.max_timeout_msec_;
basic_auth_username_ = rhs.basic_auth_username_;
basic_auth_password_ = rhs.basic_auth_password_;
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
url_encode_ = rhs.url_encode_;
address_family_ = rhs.address_family_;
tcp_nodelay_ = rhs.tcp_nodelay_;
+ ipv6_v6only_ = rhs.ipv6_v6only_;
socket_options_ = rhs.socket_options_;
compress_ = rhs.compress_;
decompress_ = rhs.decompress_;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
server_certificate_verification_ = rhs.server_certificate_verification_;
+ server_hostname_verification_ = rhs.server_hostname_verification_;
+ server_certificate_verifier_ = rhs.server_certificate_verifier_;
#endif
logger_ = rhs.logger_;
}
if (!proxy_host_.empty() && proxy_port_ != -1) {
return detail::create_client_socket(
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
- socket_options_, connection_timeout_sec_, connection_timeout_usec_,
- read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
- write_timeout_usec_, interface_, error);
+ ipv6_v6only_, socket_options_, connection_timeout_sec_,
+ connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, interface_, error);
}
// Check is custom IP specified for host_
if (it != addr_map_.end()) { ip = it->second; }
return detail::create_client_socket(
- host_, ip, port_, address_family_, tcp_nodelay_, socket_options_,
- connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
- read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
- error);
+ host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
+ socket_options_, connection_timeout_sec_, connection_timeout_usec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_, interface_, error);
}
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
auto is_alive = false;
if (socket_.is_open()) {
is_alive = detail::is_socket_alive(socket_.sock);
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ if (is_alive && is_ssl()) {
+ if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
+ is_alive = false;
+ }
+ }
+#endif
+
if (!is_alive) {
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
// like the other side has already closed the connection Also, there
auto &scli = static_cast<SSLClient &>(*this);
if (!proxy_host_.empty() && proxy_port_ != -1) {
auto success = false;
- if (!scli.connect_with_proxy(socket_, res, success, error)) {
+ if (!scli.connect_with_proxy(socket_, req.start_time_, res, success,
+ error)) {
return success;
}
}
}
});
- ret = process_socket(socket_, [&](Stream &strm) {
+ ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
return handle_request(strm, req, res, close_connection, error);
});
if (location.empty()) { return false; }
const static std::regex re(
- R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
+ R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
std::smatch m;
if (!std::regex_match(location, m, re)) { return false; }
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
+ if (!req.content_receiver) {
+ if (!req.has_header("Accept-Encoding")) {
+ std::string accept_encoding;
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ accept_encoding = "br";
+#endif
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ if (!accept_encoding.empty()) { accept_encoding += ", "; }
+ accept_encoding += "gzip, deflate";
+#endif
+ req.set_header("Accept-Encoding", accept_encoding);
+ }
+
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
- if (!req.has_header("User-Agent")) {
- auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
- req.set_header("User-Agent", agent);
- }
+ if (!req.has_header("User-Agent")) {
+ auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
+ req.set_header("User-Agent", agent);
+ }
#endif
+ };
if (req.body.empty()) {
if (req.content_provider_) {
{
detail::BufferStream bstrm;
- const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
- bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
+ const auto &path_with_query =
+ req.params.empty() ? req.path
+ : append_query_params(req.path, req.params);
+
+ const auto &path =
+ url_encode_ ? detail::encode_url(path_with_query) : path_with_query;
+
+ detail::write_request_line(bstrm, req.method, path);
header_writer_(bstrm, req.headers);
const std::string &method, const std::string &path, const Headers &headers,
const char *body, size_t content_length, ContentProvider content_provider,
ContentProviderWithoutLength content_provider_without_length,
- const std::string &content_type) {
+ const std::string &content_type, Progress progress) {
Request req;
req.method = method;
req.headers = headers;
req.path = path;
+ req.progress = progress;
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
auto error = Error::Success;
if (is_ssl()) {
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
if (!is_proxy_enabled) {
- char buf[1];
- if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
- SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
+ if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
error = Error::SSLPeerCouldBeClosed_;
return false;
}
// Body
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
req.method != "CONNECT") {
- auto redirect = 300 < res.status && res.status < 400 && follow_location_;
+ auto redirect = 300 < res.status && res.status < 400 &&
+ res.status != StatusCode::NotModified_304 &&
+ follow_location_;
if (req.response_handler && !redirect) {
if (!req.response_handler(res)) {
: static_cast<ContentReceiverWithProgress>(
[&](const char *buf, size_t n, uint64_t /*off*/,
uint64_t /*len*/) {
- if (res.body.size() + n > res.body.max_size()) {
- return false;
- }
+ assert(res.body.size() + n <= res.body.max_size());
res.body.append(buf, n);
return true;
});
return ret;
};
- int dummy_status;
- if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
- dummy_status, std::move(progress), std::move(out),
- decompress_)) {
- if (error != Error::Canceled) { error = Error::Read; }
- return false;
+ if (res.has_header("Content-Length")) {
+ if (!req.content_receiver) {
+ auto len = res.get_header_value_u64("Content-Length");
+ if (len > res.body.max_size()) {
+ error = Error::Read;
+ return false;
+ }
+ res.body.reserve(static_cast<size_t>(len));
+ }
+ }
+
+ if (res.status != StatusCode::NotModified_304) {
+ int dummy_status;
+ if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
+ dummy_status, std::move(progress),
+ std::move(out), decompress_)) {
+ if (error != Error::Canceled) { error = Error::Read; }
+ return false;
+ }
}
}
};
}
-inline bool
-ClientImpl::process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback) {
+inline bool ClientImpl::process_socket(
+ const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback) {
return detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
- write_timeout_usec_, std::move(callback));
+ write_timeout_usec_, max_timeout_msec_, start_time,
+ std::move(callback));
}
inline bool ClientImpl::is_ssl() const { return false; }
req.path = path;
req.headers = headers;
req.progress = std::move(progress);
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
return content_receiver(data, data_length);
};
req.progress = std::move(progress);
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
req.method = "HEAD";
req.headers = headers;
req.path = path;
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
inline Result ClientImpl::Post(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type) {
- return Post(path, Headers(), body, content_length, content_type);
+ return Post(path, Headers(), body, content_length, content_type, nullptr);
}
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type) {
return send_with_content_provider("POST", path, headers, body, content_length,
- nullptr, nullptr, content_type);
+ nullptr, nullptr, content_type, nullptr);
+}
+
+inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return send_with_content_provider("POST", path, headers, body, content_length,
+ nullptr, nullptr, content_type, progress);
}
inline Result ClientImpl::Post(const std::string &path, const std::string &body,
return Post(path, Headers(), body, content_type);
}
+inline Result ClientImpl::Post(const std::string &path, const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return Post(path, Headers(), body, content_type, progress);
+}
+
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return send_with_content_provider("POST", path, headers, body.data(),
- body.size(), nullptr, nullptr,
- content_type);
+ body.size(), nullptr, nullptr, content_type,
+ nullptr);
+}
+
+inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return send_with_content_provider("POST", path, headers, body.data(),
+ body.size(), nullptr, nullptr, content_type,
+ progress);
}
inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) {
const std::string &content_type) {
return send_with_content_provider("POST", path, headers, nullptr,
content_length, std::move(content_provider),
- nullptr, content_type);
+ nullptr, content_type, nullptr);
}
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
ContentProviderWithoutLength content_provider,
const std::string &content_type) {
return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr,
- std::move(content_provider), content_type);
+ std::move(content_provider), content_type,
+ nullptr);
}
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
return Post(path, headers, query, "application/x-www-form-urlencoded");
}
+inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress) {
+ auto query = detail::params_to_query_str(params);
+ return Post(path, headers, query, "application/x-www-form-urlencoded",
+ progress);
+}
+
inline Result ClientImpl::Post(const std::string &path,
const MultipartFormDataItems &items) {
return Post(path, Headers(), items);
return send_with_content_provider(
"POST", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
- content_type);
+ content_type, nullptr);
}
inline Result ClientImpl::Put(const std::string &path) {
const char *body, size_t content_length,
const std::string &content_type) {
return send_with_content_provider("PUT", path, headers, body, content_length,
- nullptr, nullptr, content_type);
+ nullptr, nullptr, content_type, nullptr);
+}
+
+inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return send_with_content_provider("PUT", path, headers, body, content_length,
+ nullptr, nullptr, content_type, progress);
}
inline Result ClientImpl::Put(const std::string &path, const std::string &body,
return Put(path, Headers(), body, content_type);
}
+inline Result ClientImpl::Put(const std::string &path, const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return Put(path, Headers(), body, content_type, progress);
+}
+
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return send_with_content_provider("PUT", path, headers, body.data(),
- body.size(), nullptr, nullptr,
- content_type);
+ body.size(), nullptr, nullptr, content_type,
+ nullptr);
+}
+
+inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return send_with_content_provider("PUT", path, headers, body.data(),
+ body.size(), nullptr, nullptr, content_type,
+ progress);
}
inline Result ClientImpl::Put(const std::string &path, size_t content_length,
const std::string &content_type) {
return send_with_content_provider("PUT", path, headers, nullptr,
content_length, std::move(content_provider),
- nullptr, content_type);
+ nullptr, content_type, nullptr);
}
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
ContentProviderWithoutLength content_provider,
const std::string &content_type) {
return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr,
- std::move(content_provider), content_type);
+ std::move(content_provider), content_type,
+ nullptr);
}
inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) {
return Put(path, headers, query, "application/x-www-form-urlencoded");
}
+inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress) {
+ auto query = detail::params_to_query_str(params);
+ return Put(path, headers, query, "application/x-www-form-urlencoded",
+ progress);
+}
+
inline Result ClientImpl::Put(const std::string &path,
const MultipartFormDataItems &items) {
return Put(path, Headers(), items);
return send_with_content_provider(
"PUT", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
- content_type);
+ content_type, nullptr);
}
inline Result ClientImpl::Patch(const std::string &path) {
return Patch(path, std::string(), std::string());
return Patch(path, Headers(), body, content_length, content_type);
}
+inline Result ClientImpl::Patch(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return Patch(path, Headers(), body, content_length, content_type, progress);
+}
+
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type) {
+ return Patch(path, headers, body, content_length, content_type, nullptr);
+}
+
+inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
return send_with_content_provider("PATCH", path, headers, body,
content_length, nullptr, nullptr,
- content_type);
+ content_type, progress);
}
inline Result ClientImpl::Patch(const std::string &path,
return Patch(path, Headers(), body, content_type);
}
+inline Result ClientImpl::Patch(const std::string &path,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return Patch(path, Headers(), body, content_type, progress);
+}
+
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
+ return Patch(path, headers, body, content_type, nullptr);
+}
+
+inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
return send_with_content_provider("PATCH", path, headers, body.data(),
- body.size(), nullptr, nullptr,
- content_type);
+ body.size(), nullptr, nullptr, content_type,
+ progress);
}
inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
const std::string &content_type) {
return send_with_content_provider("PATCH", path, headers, nullptr,
content_length, std::move(content_provider),
- nullptr, content_type);
+ nullptr, content_type, nullptr);
}
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
ContentProviderWithoutLength content_provider,
const std::string &content_type) {
return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr,
- std::move(content_provider), content_type);
+ std::move(content_provider), content_type,
+ nullptr);
}
inline Result ClientImpl::Delete(const std::string &path) {
return Delete(path, Headers(), body, content_length, content_type);
}
+inline Result ClientImpl::Delete(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return Delete(path, Headers(), body, content_length, content_type, progress);
+}
+
inline Result ClientImpl::Delete(const std::string &path,
const Headers &headers, const char *body,
size_t content_length,
const std::string &content_type) {
+ return Delete(path, headers, body, content_length, content_type, nullptr);
+}
+
+inline Result ClientImpl::Delete(const std::string &path,
+ const Headers &headers, const char *body,
+ size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
Request req;
req.method = "DELETE";
req.headers = headers;
req.path = path;
+ req.progress = progress;
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
req.body.assign(body, content_length);
return Delete(path, Headers(), body.data(), body.size(), content_type);
}
+inline Result ClientImpl::Delete(const std::string &path,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return Delete(path, Headers(), body.data(), body.size(), content_type,
+ progress);
+}
+
inline Result ClientImpl::Delete(const std::string &path,
const Headers &headers,
const std::string &body,
return Delete(path, headers, body.data(), body.size(), content_type);
}
+inline Result ClientImpl::Delete(const std::string &path,
+ const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return Delete(path, headers, body.data(), body.size(), content_type,
+ progress);
+}
+
inline Result ClientImpl::Options(const std::string &path) {
return Options(path, Headers());
}
req.method = "OPTIONS";
req.headers = headers;
req.path = path;
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
write_timeout_usec_ = usec;
}
+inline void ClientImpl::set_max_timeout(time_t msec) {
+ max_timeout_msec_ = msec;
+}
+
inline void ClientImpl::set_basic_auth(const std::string &username,
const std::string &password) {
basic_auth_username_ = username;
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
+
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
}
inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
std::size_t size) const {
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
+ auto se = detail::scope_exit([&] { BIO_free_all(mem); });
if (!mem) { return nullptr; }
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
- if (!inf) {
- BIO_free_all(mem);
- return nullptr;
- }
+ if (!inf) { return nullptr; }
auto cts = X509_STORE_new();
if (cts) {
}
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;
}
+
+inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
+ server_hostname_verification_ = enabled;
+}
+
+inline void ClientImpl::set_server_certificate_verifier(
+ std::function<bool(SSL *ssl)> verifier) {
+ server_certificate_verifier_ = verifier;
+}
#endif
inline void ClientImpl::set_logger(Logger logger) {
return ssl;
}
-inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
+inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
bool shutdown_gracefully) {
// sometimes we may want to skip this to try to avoid SIGPIPE if we know
// the remote has closed the network connection
// Note that it is not always possible to avoid SIGPIPE, this is merely a
// best-efforts.
- if (shutdown_gracefully) { SSL_shutdown(ssl); }
+ if (shutdown_gracefully) {
+#ifdef _WIN32
+ (void)(sock);
+ SSL_shutdown(ssl);
+#else
+ timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<const void *>(&tv), sizeof(tv));
+
+ auto ret = SSL_shutdown(ssl);
+ while (ret == 0) {
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+ ret = SSL_shutdown(ssl);
+ }
+#endif
+ }
std::lock_guard<std::mutex> guard(ctx_mutex);
SSL_free(ssl);
}
template <typename T>
-inline bool
-process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec, T callback) {
+inline bool process_client_socket_ssl(
+ SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
- write_timeout_sec, write_timeout_usec);
+ write_timeout_sec, write_timeout_usec,
+ max_timeout_msec, start_time);
return callback(strm);
}
};
// SSL socket stream implementation
-inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
- time_t read_timeout_sec,
- time_t read_timeout_usec,
- time_t write_timeout_sec,
- time_t write_timeout_usec)
+inline SSLSocketStream::SSLSocketStream(
+ socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time)
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
- write_timeout_usec_(write_timeout_usec) {
+ write_timeout_usec_(write_timeout_usec),
+ max_timeout_msec_(max_timeout_msec), start_time(start_time) {
SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
}
inline SSLSocketStream::~SSLSocketStream() = default;
inline bool SSLSocketStream::is_readable() const {
- return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ if (max_timeout_msec_ <= 0) {
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ }
+
+ time_t read_timeout_sec;
+ time_t read_timeout_usec;
+ calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
+ read_timeout_usec_, read_timeout_sec, read_timeout_usec);
+
+ return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
}
inline bool SSLSocketStream::is_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
- is_socket_alive(sock_);
+ is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
} else if (is_readable()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::microseconds{10});
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
}
}
return ret;
+ } else {
+ return -1;
}
- return -1;
}
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
#endif
if (is_writable()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::microseconds{10});
ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
inline socket_t SSLSocketStream::socket() const { return sock_; }
+inline time_t SSLSocketStream::duration() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time)
+ .count();
+}
+
static SSLInit sslinit_;
} // namespace detail
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
- SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+ SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
- // 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_,
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
- 1) {
+ 1 ||
+ SSL_CTX_check_private_key(ctx_) != 1) {
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
- SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+ SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
+inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store) {
+
+ std::lock_guard<std::mutex> guard(ctx_mutex_);
+
+ SSL_CTX_use_certificate(ctx_, cert);
+ SSL_CTX_use_PrivateKey(ctx_, private_key);
+
+ if (client_ca_cert_store != nullptr) {
+ SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+ }
+}
+
inline bool SSLServer::process_and_close_socket(socket_t sock) {
auto ssl = detail::ssl_new(
sock, ctx_, ctx_mutex_,
auto ret = false;
if (ssl) {
+ std::string remote_addr;
+ int remote_port = 0;
+ detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
+
+ std::string local_addr;
+ int local_port = 0;
+ detail::get_local_ip_and_port(sock, local_addr, local_port);
+
ret = detail::process_server_socket_ssl(
svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_,
- [this, ssl](Stream &strm, bool close_connection,
- bool &connection_closed) {
- return process_request(strm, close_connection, connection_closed,
+ [&](Stream &strm, bool close_connection, bool &connection_closed) {
+ return process_request(strm, remote_addr, remote_port, local_addr,
+ local_port, close_connection,
+ connection_closed,
[&](Request &req) { req.ssl = ssl; });
});
// Shutdown gracefully if the result seemed successful, non-gracefully if
// the connection appeared to be closed.
const bool shutdown_gracefully = ret;
- detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
+ detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully);
}
detail::shutdown_socket(sock);
inline SSLClient::SSLClient(const std::string &host, int port,
const std::string &client_cert_path,
- const std::string &client_key_path)
+ const std::string &client_key_path,
+ const std::string &private_key_password)
: ClientImpl(host, port, client_cert_path, client_key_path) {
ctx_ = SSL_CTX_new(TLS_client_method());
+ SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
+
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
host_components_.emplace_back(b, e);
});
if (!client_cert_path.empty() && !client_key_path.empty()) {
+ if (!private_key_password.empty()) {
+ SSL_CTX_set_default_passwd_cb_userdata(
+ ctx_, reinterpret_cast<void *>(
+ const_cast<char *>(private_key_password.c_str())));
+ }
+
if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
SSL_FILETYPE_PEM) != 1 ||
SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
}
inline SSLClient::SSLClient(const std::string &host, int port,
- X509 *client_cert, EVP_PKEY *client_key)
+ X509 *client_cert, EVP_PKEY *client_key,
+ const std::string &private_key_password)
: ClientImpl(host, port) {
ctx_ = SSL_CTX_new(TLS_client_method());
});
if (client_cert != nullptr && client_key != nullptr) {
+ if (!private_key_password.empty()) {
+ SSL_CTX_set_default_passwd_cb_userdata(
+ ctx_, reinterpret_cast<void *>(
+ const_cast<char *>(private_key_password.c_str())));
+ }
+
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
SSL_CTX_free(ctx_);
}
// Assumes that socket_mutex_ is locked and that there are no requests in flight
-inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
- bool &success, Error &error) {
+inline bool SSLClient::connect_with_proxy(
+ Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ Response &res, bool &success, Error &error) {
success = true;
Response proxy_res;
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
- write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
+ start_time, [&](Stream &strm) {
Request req2;
req2.method = "CONNECT";
req2.path = host_and_port_;
+ if (max_timeout_msec_ > 0) {
+ req2.start_time_ = std::chrono::steady_clock::now();
+ }
return process_request(strm, req2, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are no
proxy_res = Response();
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
- write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
+ start_time, [&](Stream &strm) {
Request req3;
req3.method = "CONNECT";
req3.path = host_and_port_;
req3, auth, 1, detail::random_string(10),
proxy_digest_auth_username_, proxy_digest_auth_password_,
true));
+ if (max_timeout_msec_ > 0) {
+ req3.start_time_ = std::chrono::steady_clock::now();
+ }
return process_request(strm, req3, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are
}
if (server_certificate_verification_) {
- verify_result_ = SSL_get_verify_result(ssl2);
+ if (server_certificate_verifier_) {
+ if (!server_certificate_verifier_(ssl2)) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
+ } else {
+ verify_result_ = SSL_get_verify_result(ssl2);
- if (verify_result_ != X509_V_OK) {
- error = Error::SSLServerVerification;
- return false;
- }
+ if (verify_result_ != X509_V_OK) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
- auto server_cert = SSL_get1_peer_certificate(ssl2);
+ auto server_cert = SSL_get1_peer_certificate(ssl2);
+ auto se = detail::scope_exit([&] { X509_free(server_cert); });
- if (server_cert == nullptr) {
- error = Error::SSLServerVerification;
- return false;
- }
+ if (server_cert == nullptr) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
- if (!verify_host(server_cert)) {
- X509_free(server_cert);
- error = Error::SSLServerVerification;
- return false;
+ if (server_hostname_verification_) {
+ if (!verify_host(server_cert)) {
+ error = Error::SSLServerHostnameVerification;
+ return false;
+ }
+ }
}
- X509_free(server_cert);
}
return true;
},
[&](SSL *ssl2) {
+#if defined(OPENSSL_IS_BORINGSSL)
+ SSL_set_tlsext_host_name(ssl2, host_.c_str());
+#else
// NOTE: Direct call instead of using the OpenSSL macro to suppress
// -Wold-style-cast warning
- // SSL_set_tlsext_host_name(ssl2, host_.c_str());
SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
static_cast<void *>(const_cast<char *>(host_.c_str())));
+#endif
return true;
});
return;
}
if (socket.ssl) {
- detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
+ detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock,
+ shutdown_gracefully);
socket.ssl = nullptr;
}
assert(socket.ssl == nullptr);
}
-inline bool
-SSLClient::process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback) {
+inline bool SSLClient::process_socket(
+ const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback) {
assert(socket.ssl);
return detail::process_client_socket_ssl(
socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
- write_timeout_sec_, write_timeout_usec_, std::move(callback));
+ write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
+ std::move(callback));
}
inline bool SSLClient::is_ssl() const { return true; }
auto type = GEN_DNS;
- struct in6_addr addr6 {};
- struct in_addr addr {};
+ struct in6_addr addr6{};
+ struct in_addr addr{};
size_t addr_len = 0;
#ifndef __MINGW32__
const std::string &client_cert_path,
const std::string &client_key_path) {
const static std::regex re(
- R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
+ R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
std::smatch m;
if (std::regex_match(scheme_host_port, m, re)) {
client_key_path);
}
} else {
+ // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
+ // if port param below changes.
cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
client_cert_path, client_key_path);
}
-}
+} // namespace detail
inline Client::Client(const std::string &host, int port)
: cli_(detail::make_unique<ClientImpl>(host, port)) {}
const std::string &content_type) {
return cli_->Post(path, headers, body, content_length, content_type);
}
+inline Result Client::Post(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress) {
+ return cli_->Post(path, headers, body, content_length, content_type,
+ progress);
+}
inline Result Client::Post(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Post(path, body, content_type);
}
+inline Result Client::Post(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress) {
+ return cli_->Post(path, body, content_type, progress);
+}
inline Result Client::Post(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return cli_->Post(path, headers, body, content_type);
}
+inline Result Client::Post(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type, Progress progress) {
+ return cli_->Post(path, headers, body, content_type, progress);
+}
inline Result Client::Post(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type) {
const Params ¶ms) {
return cli_->Post(path, headers, params);
}
+inline Result Client::Post(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress) {
+ return cli_->Post(path, headers, params, progress);
+}
inline Result Client::Post(const std::string &path,
const MultipartFormDataItems &items) {
return cli_->Post(path, items);
const std::string &content_type) {
return cli_->Put(path, headers, body, content_length, content_type);
}
+inline Result Client::Put(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type, Progress progress) {
+ return cli_->Put(path, headers, body, content_length, content_type, progress);
+}
inline Result Client::Put(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Put(path, body, content_type);
}
+inline Result Client::Put(const std::string &path, const std::string &body,
+ const std::string &content_type, Progress progress) {
+ return cli_->Put(path, body, content_type, progress);
+}
inline Result Client::Put(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return cli_->Put(path, headers, body, content_type);
}
+inline Result Client::Put(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type, Progress progress) {
+ return cli_->Put(path, headers, body, content_type, progress);
+}
inline Result Client::Put(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type) {
const Params ¶ms) {
return cli_->Put(path, headers, params);
}
+inline Result Client::Put(const std::string &path, const Headers &headers,
+ const Params ¶ms, Progress progress) {
+ return cli_->Put(path, headers, params, progress);
+}
inline Result Client::Put(const std::string &path,
const MultipartFormDataItems &items) {
return cli_->Put(path, items);
const std::string &content_type) {
return cli_->Patch(path, body, content_length, content_type);
}
+inline Result Client::Patch(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Patch(path, body, content_length, content_type, progress);
+}
inline Result Client::Patch(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type) {
return cli_->Patch(path, headers, body, content_length, content_type);
}
+inline Result Client::Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Patch(path, headers, body, content_length, content_type,
+ progress);
+}
inline Result Client::Patch(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Patch(path, body, content_type);
}
+inline Result Client::Patch(const std::string &path, const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Patch(path, body, content_type, progress);
+}
inline Result Client::Patch(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return cli_->Patch(path, headers, body, content_type);
}
+inline Result Client::Patch(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Patch(path, headers, body, content_type, progress);
+}
inline Result Client::Patch(const std::string &path, size_t content_length,
ContentProvider content_provider,
const std::string &content_type) {
const std::string &content_type) {
return cli_->Delete(path, body, content_length, content_type);
}
+inline Result Client::Delete(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Delete(path, body, content_length, content_type, progress);
+}
inline Result Client::Delete(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type) {
return cli_->Delete(path, headers, body, content_length, content_type);
}
+inline Result Client::Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Delete(path, headers, body, content_length, content_type,
+ progress);
+}
inline Result Client::Delete(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Delete(path, body, content_type);
}
+inline Result Client::Delete(const std::string &path, const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Delete(path, body, content_type, progress);
+}
inline Result Client::Delete(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type) {
return cli_->Delete(path, headers, body, content_type);
}
+inline Result Client::Delete(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type,
+ Progress progress) {
+ return cli_->Delete(path, headers, body, content_type, progress);
+}
inline Result Client::Options(const std::string &path) {
return cli_->Options(path);
}
inline void Client::enable_server_certificate_verification(bool enabled) {
cli_->enable_server_certificate_verification(enabled);
}
+
+inline void Client::enable_server_hostname_verification(bool enabled) {
+ cli_->enable_server_hostname_verification(enabled);
+}
+
+inline void Client::set_server_certificate_verifier(
+ std::function<bool(SSL *ssl)> verifier) {
+ cli_->set_server_certificate_verifier(verifier);
+}
#endif
inline void Client::set_logger(Logger logger) {