//
// httplib.h
//
-// Copyright (c) 2023 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.14.1"
+#define CPPHTTPLIB_VERSION "0.20.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_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
#endif
+#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
+#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
+#endif
+
#ifndef CPPHTTPLIB_TCP_NODELAY
#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
#endif // _MSC_VER
#ifndef S_ISREG
-#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
+#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
#endif // S_ISREG
#ifndef S_ISDIR
-#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
+#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
#endif // S_ISDIR
#ifndef NOMINMAX
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
#endif
-#ifndef strcasecmp
-#define strcasecmp _stricmp
-#endif // strcasecmp
-
+using nfds_t = unsigned long;
using socket_t = SOCKET;
-#ifdef CPPHTTPLIB_USE_POLL
-#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
-#endif
+using socklen_t = int;
#else // not _WIN32
#ifdef __linux__
#include <resolv.h>
#endif
+#include <csignal>
#include <netinet/tcp.h>
-#ifdef CPPHTTPLIB_USE_POLL
#include <poll.h>
-#endif
-#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
-#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <condition_variable>
#include <cstring>
#include <errno.h>
+#include <exception>
#include <fcntl.h>
-#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#ifdef _MSC_VER
#pragma comment(lib, "crypt32.lib")
-#pragma comment(lib, "cryptui.lib")
#endif
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
#include <TargetConditionals.h>
#include <iostream>
#include <sstream>
-#if OPENSSL_VERSION_NUMBER < 0x1010100fL
-#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
-#elif 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
#endif
#include <brotli/encode.h>
#endif
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+#include <zstd.h>
+#endif
+
/*
* Declaration
*/
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".
explicit scope_exit(std::function<void(void)> &&f)
: exit_function(std::move(f)), execute_on_destruction{true} {}
- scope_exit(scope_exit &&rhs)
+ scope_exit(scope_exit &&rhs) noexcept
: exit_function(std::move(rhs.exit_function)),
execute_on_destruction{rhs.execute_on_destruction} {
rhs.release();
} // namespace detail
-using Headers = std::multimap<std::string, std::string, detail::ci>;
+enum SSLVerifierResponse {
+ // no decision has been made, use the built-in certificate verifier
+ NoDecisionMade,
+ // connection certificate is verified and accepted
+ CertificateAccepted,
+ // connection certificate was processed but is rejected
+ CertificateRejected
+};
+
+enum StatusCode {
+ // Information responses
+ Continue_100 = 100,
+ SwitchingProtocol_101 = 101,
+ Processing_102 = 102,
+ EarlyHints_103 = 103,
+
+ // Successful responses
+ OK_200 = 200,
+ Created_201 = 201,
+ Accepted_202 = 202,
+ NonAuthoritativeInformation_203 = 203,
+ NoContent_204 = 204,
+ ResetContent_205 = 205,
+ PartialContent_206 = 206,
+ MultiStatus_207 = 207,
+ AlreadyReported_208 = 208,
+ IMUsed_226 = 226,
+
+ // Redirection messages
+ MultipleChoices_300 = 300,
+ MovedPermanently_301 = 301,
+ Found_302 = 302,
+ SeeOther_303 = 303,
+ NotModified_304 = 304,
+ UseProxy_305 = 305,
+ unused_306 = 306,
+ TemporaryRedirect_307 = 307,
+ PermanentRedirect_308 = 308,
+
+ // Client error responses
+ BadRequest_400 = 400,
+ Unauthorized_401 = 401,
+ PaymentRequired_402 = 402,
+ Forbidden_403 = 403,
+ NotFound_404 = 404,
+ MethodNotAllowed_405 = 405,
+ NotAcceptable_406 = 406,
+ ProxyAuthenticationRequired_407 = 407,
+ RequestTimeout_408 = 408,
+ Conflict_409 = 409,
+ Gone_410 = 410,
+ LengthRequired_411 = 411,
+ PreconditionFailed_412 = 412,
+ PayloadTooLarge_413 = 413,
+ UriTooLong_414 = 414,
+ UnsupportedMediaType_415 = 415,
+ RangeNotSatisfiable_416 = 416,
+ ExpectationFailed_417 = 417,
+ ImATeapot_418 = 418,
+ MisdirectedRequest_421 = 421,
+ UnprocessableContent_422 = 422,
+ Locked_423 = 423,
+ FailedDependency_424 = 424,
+ TooEarly_425 = 425,
+ UpgradeRequired_426 = 426,
+ PreconditionRequired_428 = 428,
+ TooManyRequests_429 = 429,
+ RequestHeaderFieldsTooLarge_431 = 431,
+ UnavailableForLegalReasons_451 = 451,
+
+ // Server error responses
+ InternalServerError_500 = 500,
+ NotImplemented_501 = 501,
+ BadGateway_502 = 502,
+ ServiceUnavailable_503 = 503,
+ GatewayTimeout_504 = 504,
+ HttpVersionNotSupported_505 = 505,
+ VariantAlsoNegotiates_506 = 506,
+ InsufficientStorage_507 = 507,
+ LoopDetected_508 = 508,
+ NotExtended_510 = 510,
+ NetworkAuthenticationRequired_511 = 511,
+};
+
+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;
DataSink &operator=(DataSink &&) = delete;
std::function<bool(const char *data, size_t data_len)> write;
+ std::function<bool()> is_writable;
std::function<void()> done;
std::function<void(const Headers &trailer)> done_with_trailer;
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) {}
protected:
- std::streamsize xsputn(const char *s, std::streamsize n) {
+ std::streamsize xsputn(const char *s, std::streamsize n) override {
sink_.write(s, static_cast<size_t>(n));
return n;
}
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);
- void set_redirect(const std::string &url, int status = 302);
+ void set_redirect(const std::string &url, int status = StatusCode::Found_302);
void set_content(const char *s, size_t n, const std::string &content_type);
void set_content(const std::string &s, const std::string &content_type);
+ void set_content(std::string &&s, const std::string &content_type);
void set_content_provider(
size_t length, const std::string &content_type, ContentProvider provider,
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 ~Stream() = default;
virtual bool is_readable() const = 0;
- virtual bool is_writable() const = 0;
+ virtual bool wait_readable() const = 0;
+ virtual bool wait_writable() const = 0;
virtual ssize_t read(char *ptr, size_t size) = 0;
virtual ssize_t write(const char *ptr, size_t size) = 0;
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);
};
TaskQueue() = default;
virtual ~TaskQueue() = default;
- virtual void enqueue(std::function<void()> fn) = 0;
+ virtual bool enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
virtual void on_idle() {}
};
-class ThreadPool : public TaskQueue {
+class ThreadPool final : public TaskQueue {
public:
- explicit ThreadPool(size_t n) : shutdown_(false) {
+ explicit ThreadPool(size_t n, size_t mqr = 0)
+ : shutdown_(false), max_queued_requests_(mqr) {
while (n) {
threads_.emplace_back(worker(*this));
n--;
ThreadPool(const ThreadPool &) = delete;
~ThreadPool() override = default;
- void enqueue(std::function<void()> fn) override {
+ bool enqueue(std::function<void()> fn) override {
{
std::unique_lock<std::mutex> lock(mutex_);
+ if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
+ return false;
+ }
jobs_.push_back(std::move(fn));
}
cond_.notify_one();
+ return true;
}
void shutdown() override {
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_;
std::list<std::function<void()>> jobs_;
bool shutdown_;
+ size_t max_queued_requests_ = 0;
std::condition_variable cond_;
std::mutex mutex_;
using SocketOptions = std::function<void(socket_t sock)>;
+namespace detail {
+
+bool set_socket_opt_impl(socket_t sock, int level, int optname,
+ const void *optval, socklen_t optlen);
+bool set_socket_opt(socket_t sock, int level, int optname, int opt);
+bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
+ time_t usec);
+
+} // namespace detail
+
void default_socket_options(socket_t sock);
const char *status_message(int status);
+std::string get_bearer_token_auth(const Request &req);
+
namespace detail {
class MatcherBase {
* Captures parameters in request path and stores them in Request::path_params
*
* Capture name is a substring of a pattern from : to /.
- * The rest of the pattern is matched agains the request path directly
+ * The rest of the pattern is matched against the request path directly
* Parameters are captured starting from the next character after
* the end of the last matched static pattern fragment until the next /.
*
* 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;
bool routing(Request &req, Response &res, Stream &strm);
bool handle_file_request(const Request &req, Response &res,
bool head = false);
- bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
- bool
- dispatch_request_for_content_reader(Request &req, Response &res,
- ContentReader content_reader,
- const HandlersForContentReader &handlers);
+ bool dispatch_request(Request &req, Response &res,
+ const Handlers &handlers) const;
+ bool dispatch_request_for_content_reader(
+ Request &req, Response &res, ContentReader content_reader,
+ const HandlersForContentReader &handlers) const;
- bool parse_request_line(const char *s, Request &req);
+ bool parse_request_line(const char *s, Request &req) const;
void apply_ranges(const Request &req, Response &res,
- std::string &content_type, std::string &boundary);
- bool write_response(Stream &strm, bool close_connection, const Request &req,
+ std::string &content_type, std::string &boundary) const;
+ bool write_response(Stream &strm, bool close_connection, Request &req,
Response &res);
bool write_response_with_content(Stream &strm, bool close_connection,
const Request &req, Response &res);
bool read_content_core(Stream &strm, Request &req, Response &res,
ContentReceiver receiver,
MultipartContentHeader multipart_header,
- ContentReceiver multipart_receiver);
+ ContentReceiver multipart_receiver) 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_decommissioned{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,
SSLPeerCouldBeClosed_,
};
-std::string to_string(const Error error);
+std::string to_string(Error error);
std::ostream &operator<<(std::ostream &os, const Error &obj);
// 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
void set_ca_cert_path(const std::string &ca_cert_file_path,
const std::string &ca_cert_dir_path = std::string());
void set_ca_cert_store(X509_STORE *ca_cert_store);
- X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size);
+ X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const;
#endif
#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<SSLVerifierResponse(SSL *ssl)> verifier);
#endif
void set_logger(Logger logger);
// Also, shutdown_ssl and close_socket should also NOT be called concurrently
// with a DIFFERENT thread sending requests using that socket.
virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
- void shutdown_socket(Socket &socket);
+ void shutdown_socket(Socket &socket) const;
void close_socket(Socket &socket);
bool process_request(Stream &strm, Request &req, Response &res,
bool close_connection, Error &error);
bool write_content_with_provider(Stream &strm, const Request &req,
- Error &error);
+ Error &error) const;
void copy_settings(const ClientImpl &rhs);
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<SSLVerifierResponse(SSL *ssl)> server_certificate_verifier_;
#endif
Logger logger_;
Result send_(Request &&req);
socket_t create_client_socket(Error &error) const;
- bool read_response_line(Stream &strm, const Request &req, Response &res);
+ bool read_response_line(Stream &strm, const Request &req,
+ Response &res) const;
bool write_request(Stream &strm, Request &req, bool close_connection,
Error &error);
bool redirect(Request &req, Response &res, Error &error);
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 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<SSLVerifierResponse(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;
private:
bool create_and_connect_socket(Socket &socket, Error &error) override;
void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
- void shutdown_ssl_impl(Socket &socket, bool shutdown_socket);
+ 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));
}
+template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
+ return N - 1;
+}
+
+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);
+ uint64_t def, size_t id) const {
+ return detail::get_header_value_u64(headers, key, def, id);
}
-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);
+namespace detail {
- if (n >= buf.size() - 1) {
- std::vector<char> glowable_buf(buf.size());
+inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
+ const void *optval, socklen_t optlen) {
+ return setsockopt(sock, level, optname,
+#ifdef _WIN32
+ reinterpret_cast<const char *>(optval),
+#else
+ optval,
+#endif
+ optlen) == 0;
+}
- 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);
- }
+inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
+ return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval));
}
-inline void default_socket_options(socket_t sock) {
- int yes = 1;
+inline bool set_socket_opt_time(socket_t sock, int level, int optname,
+ time_t sec, time_t usec) {
#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));
+ auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
#else
+ timeval timeout;
+ timeout.tv_sec = static_cast<long>(sec);
+ timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
+#endif
+ return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
+}
+
+} // namespace detail
+
+inline void default_socket_options(socket_t sock) {
+ detail::set_socket_opt(sock, SOL_SOCKET,
#ifdef SO_REUSEPORT
- setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
+ SO_REUSEPORT,
#else
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
-#endif
+ SO_REUSEADDR,
#endif
+ 1);
}
inline const char *status_message(int status) {
switch (status) {
- case 100: return "Continue";
- case 101: return "Switching Protocol";
- case 102: return "Processing";
- case 103: return "Early Hints";
- case 200: return "OK";
- case 201: return "Created";
- case 202: return "Accepted";
- case 203: return "Non-Authoritative Information";
- case 204: return "No Content";
- case 205: return "Reset Content";
- case 206: return "Partial Content";
- case 207: return "Multi-Status";
- case 208: return "Already Reported";
- case 226: return "IM Used";
- case 300: return "Multiple Choice";
- case 301: return "Moved Permanently";
- case 302: return "Found";
- case 303: return "See Other";
- case 304: return "Not Modified";
- case 305: return "Use Proxy";
- case 306: return "unused";
- case 307: return "Temporary Redirect";
- case 308: return "Permanent Redirect";
- case 400: return "Bad Request";
- case 401: return "Unauthorized";
- case 402: return "Payment Required";
- case 403: return "Forbidden";
- case 404: return "Not Found";
- case 405: return "Method Not Allowed";
- case 406: return "Not Acceptable";
- case 407: return "Proxy Authentication Required";
- case 408: return "Request Timeout";
- case 409: return "Conflict";
- case 410: return "Gone";
- case 411: return "Length Required";
- case 412: return "Precondition Failed";
- case 413: return "Payload Too Large";
- case 414: return "URI Too Long";
- case 415: return "Unsupported Media Type";
- case 416: return "Range Not Satisfiable";
- case 417: return "Expectation Failed";
- case 418: return "I'm a teapot";
- case 421: return "Misdirected Request";
- case 422: return "Unprocessable Entity";
- case 423: return "Locked";
- case 424: return "Failed Dependency";
- case 425: return "Too Early";
- case 426: return "Upgrade Required";
- case 428: return "Precondition Required";
- case 429: return "Too Many Requests";
- case 431: return "Request Header Fields Too Large";
- case 451: return "Unavailable For Legal Reasons";
- case 501: return "Not Implemented";
- case 502: return "Bad Gateway";
- case 503: return "Service Unavailable";
- case 504: return "Gateway Timeout";
- case 505: return "HTTP Version Not Supported";
- case 506: return "Variant Also Negotiates";
- case 507: return "Insufficient Storage";
- case 508: return "Loop Detected";
- case 510: return "Not Extended";
- case 511: return "Network Authentication Required";
+ case StatusCode::Continue_100: return "Continue";
+ case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
+ case StatusCode::Processing_102: return "Processing";
+ case StatusCode::EarlyHints_103: return "Early Hints";
+ case StatusCode::OK_200: return "OK";
+ case StatusCode::Created_201: return "Created";
+ case StatusCode::Accepted_202: return "Accepted";
+ case StatusCode::NonAuthoritativeInformation_203:
+ return "Non-Authoritative Information";
+ case StatusCode::NoContent_204: return "No Content";
+ case StatusCode::ResetContent_205: return "Reset Content";
+ case StatusCode::PartialContent_206: return "Partial Content";
+ case StatusCode::MultiStatus_207: return "Multi-Status";
+ case StatusCode::AlreadyReported_208: return "Already Reported";
+ case StatusCode::IMUsed_226: return "IM Used";
+ case StatusCode::MultipleChoices_300: return "Multiple Choices";
+ case StatusCode::MovedPermanently_301: return "Moved Permanently";
+ case StatusCode::Found_302: return "Found";
+ case StatusCode::SeeOther_303: return "See Other";
+ case StatusCode::NotModified_304: return "Not Modified";
+ case StatusCode::UseProxy_305: return "Use Proxy";
+ case StatusCode::unused_306: return "unused";
+ case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
+ case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
+ case StatusCode::BadRequest_400: return "Bad Request";
+ case StatusCode::Unauthorized_401: return "Unauthorized";
+ case StatusCode::PaymentRequired_402: return "Payment Required";
+ case StatusCode::Forbidden_403: return "Forbidden";
+ case StatusCode::NotFound_404: return "Not Found";
+ case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
+ case StatusCode::NotAcceptable_406: return "Not Acceptable";
+ case StatusCode::ProxyAuthenticationRequired_407:
+ return "Proxy Authentication Required";
+ case StatusCode::RequestTimeout_408: return "Request Timeout";
+ case StatusCode::Conflict_409: return "Conflict";
+ case StatusCode::Gone_410: return "Gone";
+ case StatusCode::LengthRequired_411: return "Length Required";
+ case StatusCode::PreconditionFailed_412: return "Precondition Failed";
+ case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
+ case StatusCode::UriTooLong_414: return "URI Too Long";
+ case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
+ case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
+ case StatusCode::ExpectationFailed_417: return "Expectation Failed";
+ case StatusCode::ImATeapot_418: return "I'm a teapot";
+ case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
+ case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
+ case StatusCode::Locked_423: return "Locked";
+ case StatusCode::FailedDependency_424: return "Failed Dependency";
+ case StatusCode::TooEarly_425: return "Too Early";
+ case StatusCode::UpgradeRequired_426: return "Upgrade Required";
+ case StatusCode::PreconditionRequired_428: return "Precondition Required";
+ case StatusCode::TooManyRequests_429: return "Too Many Requests";
+ case StatusCode::RequestHeaderFieldsTooLarge_431:
+ return "Request Header Fields Too Large";
+ case StatusCode::UnavailableForLegalReasons_451:
+ return "Unavailable For Legal Reasons";
+ case StatusCode::NotImplemented_501: return "Not Implemented";
+ case StatusCode::BadGateway_502: return "Bad Gateway";
+ case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
+ case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
+ case StatusCode::HttpVersionNotSupported_505:
+ return "HTTP Version Not Supported";
+ case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
+ case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
+ case StatusCode::LoopDetected_508: return "Loop Detected";
+ case StatusCode::NotExtended_510: return "Not Extended";
+ case StatusCode::NetworkAuthenticationRequired_511:
+ return "Network Authentication Required";
default:
- case 500: return "Internal Server Error";
+ case StatusCode::InternalServerError_500: return "Internal Server Error";
+ }
+}
+
+inline std::string get_bearer_token_auth(const Request &req) {
+ if (req.has_header("Authorization")) {
+ constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
+ return req.get_header_value("Authorization")
+ .substr(bearer_header_prefix_len);
}
+ return "";
}
template <class Rep, class Period>
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.
std::string append_query_params(const std::string &path, const Params ¶ms);
-std::pair<std::string, std::string> make_range_header(Ranges ranges);
+std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
std::pair<std::string, std::string>
make_basic_authentication_header(const std::string &username,
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);
-void read_file(const std::string &path, std::string &out);
-
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);
-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);
+void split(const char *b, const char *e, char d, size_t m,
+ std::function<void(const char *, const char *)> fn);
-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,
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
-enum class EncodingType { None = 0, Gzip, Brotli };
+enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
EncodingType encoding_type(const Request &req, const Response &res);
-class BufferStream : public Stream {
+class BufferStream final : public Stream {
public:
BufferStream() = default;
~BufferStream() override = default;
bool is_readable() const override;
- bool is_writable() const override;
+ bool wait_readable() const override;
+ bool wait_writable() const override;
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) 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;
const std::string &get_buffer() const;
Callback callback) = 0;
};
-class nocompressor : public compressor {
+class nocompressor final : public compressor {
public:
- virtual ~nocompressor() = default;
+ ~nocompressor() override = default;
bool compress(const char *data, size_t data_length, bool /*last*/,
Callback callback) override;
};
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-class gzip_compressor : public compressor {
+class gzip_compressor final : public compressor {
public:
gzip_compressor();
- ~gzip_compressor();
+ ~gzip_compressor() override;
bool compress(const char *data, size_t data_length, bool last,
Callback callback) override;
z_stream strm_;
};
-class gzip_decompressor : public decompressor {
+class gzip_decompressor final : public decompressor {
public:
gzip_decompressor();
- ~gzip_decompressor();
+ ~gzip_decompressor() override;
bool is_valid() const 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();
};
#endif
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+class zstd_compressor : public compressor {
+public:
+ zstd_compressor();
+ ~zstd_compressor();
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override;
+
+private:
+ ZSTD_CCtx *ctx_ = nullptr;
+};
+
+class zstd_decompressor : public decompressor {
+public:
+ zstd_decompressor();
+ ~zstd_decompressor();
+
+ bool is_valid() const override;
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override;
+
+private:
+ ZSTD_DCtx *ctx_ = nullptr;
+};
+#endif
+
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
// to store data. The call can set memory on stack for performance.
class stream_line_reader {
char *fixed_buffer_;
const size_t fixed_buffer_size_;
size_t fixed_buffer_used_size_ = 0;
- std::string glowable_buffer_;
+ std::string growable_buffer_;
};
class mmap {
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
// ----------------------------------------------------------------------------
inline size_t to_utf8(int code, char *buff) {
if (code < 0x0080) {
- buff[0] = (code & 0x7F);
+ buff[0] = static_cast<char>(code & 0x7F);
return 1;
} else if (code < 0x0800) {
buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
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;
// Read component
auto beg = i;
while (i < path.size() && path[i] != '/') {
+ if (path[i] == '\0') {
+ return false;
+ } else if (path[i] == '\\') {
+ return false;
+ }
i++;
}
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 result;
}
-inline void read_file(const std::string &path, std::string &out) {
- std::ifstream fs(path, std::ios_base::binary);
- fs.seekg(0, std::ios_base::end);
- auto size = fs.tellg();
- fs.seekg(0);
- out.resize(static_cast<size_t>(size));
- fs.read(&out[0], static_cast<std::streamsize>(size));
-}
-
inline std::string file_extension(const std::string &path) {
std::smatch m;
- static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
+ thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
if (std::regex_search(path, m, re)) { return m[1].str(); }
return std::string();
}
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));
+}
+
+inline void split(const char *b, const char *e, char d, size_t m,
+ std::function<void(const char *, const char *)> fn) {
size_t i = 0;
size_t beg = 0;
+ size_t count = 1;
while (e ? (b + i < e) : (b[i] != '\0')) {
- if (b[i] == d) {
+ if (b[i] == d && count < m) {
auto r = trim(b, e, beg, i);
if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
beg = i + 1;
+ count++;
}
i++;
}
fixed_buffer_size_(fixed_buffer_size) {}
inline const char *stream_line_reader::ptr() const {
- if (glowable_buffer_.empty()) {
+ if (growable_buffer_.empty()) {
return fixed_buffer_;
} else {
- return glowable_buffer_.data();
+ return growable_buffer_.data();
}
}
inline size_t stream_line_reader::size() const {
- if (glowable_buffer_.empty()) {
+ if (growable_buffer_.empty()) {
return fixed_buffer_used_size_;
} else {
- return glowable_buffer_.size();
+ return growable_buffer_.size();
}
}
inline bool stream_line_reader::getline() {
fixed_buffer_used_size_ = 0;
- glowable_buffer_.clear();
+ growable_buffer_.clear();
+
+#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+ char prev_byte = 0;
+#endif
for (size_t i = 0;; i++) {
char byte;
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;
fixed_buffer_[fixed_buffer_used_size_++] = c;
fixed_buffer_[fixed_buffer_used_size_] = '\0';
} else {
- if (glowable_buffer_.empty()) {
+ if (growable_buffer_.empty()) {
assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
- glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
+ growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
}
- glowable_buffer_ += c;
+ growable_buffer_ += c;
}
}
-inline mmap::mmap(const char *path)
-#if defined(_WIN32)
- : hFile_(NULL), hMapping_(NULL)
-#else
- : fd_(-1)
-#endif
- ,
- size_(0), addr_(nullptr) {
- if (!open(path)) { std::runtime_error(""); }
-}
+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);
-
- hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
-
- if (hMapping_ == NULL) {
- close();
+ 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);
+#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 (const char *)addr_; }
+inline const char *mmap::data() const {
+ return is_open_empty_file ? "" : static_cast<const char *>(addr_);
+}
inline void mmap::close() {
#if defined(_WIN32)
::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) {
-#ifdef CPPHTTPLIB_USE_POLL
- struct pollfd pfd_read;
- pfd_read.fd = sock;
- pfd_read.events = POLLIN;
-
- auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
-
- return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
+#ifdef _WIN32
+ return ::WSAPoll(fds, nfds, 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);
-
- return handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
- });
+ return ::poll(fds, nfds, timeout);
#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;
+template <bool Read>
+inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
+ 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); });
-#else
-#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return 1; }
-#endif
-
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(sock, &fds);
+ return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
+}
- 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,
time_t usec) {
-#ifdef CPPHTTPLIB_USE_POLL
struct pollfd pfd_read;
pfd_read.fd = sock;
pfd_read.events = POLLIN | POLLOUT;
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+ auto poll_res =
+ handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
if (poll_res == 0) { return Error::ConnectionTimeout; }
}
return Error::Connection;
-#else
-#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return Error::Connection; }
-#endif
-
- fd_set fdsr;
- FD_ZERO(&fdsr);
- FD_SET(sock, &fdsr);
-
- auto fdsw = fdsr;
- auto fdse = fdsr;
-
- timeval tv;
- tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
-
- auto ret = handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
- });
-
- if (ret == 0) { return Error::ConnectionTimeout; }
-
- if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
- auto error = 0;
- socklen_t len = sizeof(error);
- auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
- reinterpret_cast<char *>(&error), &len);
- auto successful = res >= 0 && !error;
- return successful ? Error::Success : Error::Connection;
- }
- return Error::Connection;
-#endif
}
inline bool is_socket_alive(socket_t sock) {
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;
- bool is_writable() const override;
+ bool wait_readable() const override;
+ bool wait_writable() const override;
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) 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;
size_t read_buff_content_size_ = 0;
- static const size_t read_buff_size_ = 1024 * 4;
+ static const size_t read_buff_size_ = 1024l * 4;
};
#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;
- bool is_writable() const override;
+ bool wait_readable() const override;
+ bool wait_writable() const override;
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) 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();
#ifndef _WIN32
if (hints.ai_family == AF_UNIX) {
const auto addrlen = host.length();
- if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET;
+ 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;
-#ifdef _WIN32
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const char *>(&yes), sizeof(yes));
-#else
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const void *>(&yes), sizeof(yes));
-#endif
- }
-
- if (socket_options) { socket_options(sock); }
+ if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
if (rp->ai_family == AF_INET6) {
- auto no = 0;
-#ifdef _WIN32
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const char *>(&no), sizeof(no));
-#else
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const void *>(&no), sizeof(no));
-#endif
+ set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
}
+ 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);
if (ip_from_if.empty()) { ip_from_if = intf; }
- if (!bind_ip_address(sock2, ip_from_if.c_str())) {
+ if (!bind_ip_address(sock2, ip_from_if)) {
error = Error::BindIPAddress;
return false;
}
}
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);
-
- {
-#ifdef _WIN32
- auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
- read_timeout_usec / 1000);
- setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
- reinterpret_cast<const char *>(&timeout), sizeof(timeout));
-#else
- timeval tv;
- tv.tv_sec = static_cast<long>(read_timeout_sec);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
- setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
- reinterpret_cast<const void *>(&tv), sizeof(tv));
-#endif
- }
- {
-
-#ifdef _WIN32
- auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
- write_timeout_usec / 1000);
- setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
- reinterpret_cast<const char *>(&timeout), sizeof(timeout));
-#else
- timeval tv;
- tv.tv_sec = static_cast<long>(write_timeout_sec);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
- setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
- reinterpret_cast<const void *>(&tv), sizeof(tv));
-#endif
- }
+ set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
+ read_timeout_usec);
+ set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
+ write_timeout_usec);
error = Error::Success;
return true;
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);
}
auto ext = file_extension(path);
auto it = user_data.find(ext);
- if (it != user_data.end()) { return it->second.c_str(); }
+ if (it != user_data.end()) { return it->second; }
using udl::operator""_t;
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);
}
}
if (ret) { return EncodingType::Gzip; }
#endif
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+ // TODO: 'Accept-Encoding' has zstd, not zstd;q=0
+ ret = s.find("zstd") != std::string::npos;
+ if (ret) { return EncodingType::Zstd; }
+#endif
+
return EncodingType::None;
}
}
}
- if (ret != Z_OK && ret != Z_STREAM_END) return false;
+ if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
} while (data_length > 0);
}
#endif
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+inline zstd_compressor::zstd_compressor() {
+ ctx_ = ZSTD_createCCtx();
+ ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
+}
+
+inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
+
+inline bool zstd_compressor::compress(const char *data, size_t data_length,
+ bool last, Callback callback) {
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+
+ ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
+ ZSTD_inBuffer input = {data, data_length, 0};
+
+ bool finished;
+ do {
+ ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
+ size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
+
+ if (ZSTD_isError(remaining)) { return false; }
+
+ if (!callback(buff.data(), output.pos)) { return false; }
+
+ finished = last ? (remaining == 0) : (input.pos == input.size);
+
+ } while (!finished);
+
+ return true;
+}
+
+inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
+
+inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
+
+inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
+
+inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
+ Callback callback) {
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ ZSTD_inBuffer input = {data, data_length, 0};
+
+ while (input.pos < input.size) {
+ ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
+ size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
+
+ if (ZSTD_isError(remaining)) { return false; }
+
+ if (!callback(buff.data(), output.pos)) { return false; }
+ }
+
+ return true;
+}
+#endif
+
inline bool has_header(const Headers &headers, const std::string &key) {
return headers.find(key) != headers.end();
}
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++;
}
+ auto name = std::string(beg, p);
+ if (!detail::fields::is_field_name(name)) { return false; }
+
if (p == end) { return false; }
auto key_end = p;
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 = 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 false;
- } else if (n == 0) {
- return true;
- }
+ 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);
if (!line_reader.getline()) { return false; }
- if (strcmp(line_reader.ptr(), "\r\n")) { return false; }
+ if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; }
if (!line_reader.getline()) { return false; }
}
assert(chunk_len == 0);
- // Trailer
- if (!line_reader.getline()) { return false; }
-
- while (strcmp(line_reader.ptr(), "\r\n")) {
+ // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "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-httplib now allows
+ // chunked 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; }
// 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) {
- 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 !strcasecmp(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>
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
decompressor = detail::make_unique<gzip_decompressor>();
#else
- status = 415;
+ status = StatusCode::UnsupportedMediaType_415;
return false;
#endif
} else if (encoding.find("br") != std::string::npos) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
decompressor = detail::make_unique<brotli_decompressor>();
#else
- status = 415;
+ status = StatusCode::UnsupportedMediaType_415;
+ return false;
+#endif
+ } else if (encoding == "zstd") {
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+ decompressor = detail::make_unique<zstd_decompressor>();
+#else
+ status = StatusCode::UnsupportedMediaType_415;
return false;
#endif
}
};
return callback(std::move(out));
} else {
- status = 500;
+ status = StatusCode::InternalServerError_500;
return false;
}
}
} 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;
}
}
- if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ if (!ret) {
+ status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
+ : StatusCode::BadRequest_400;
+ }
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;
}
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
- if (strm.is_writable() && write_data(strm, d, l)) {
+ if (write_data(strm, d, l)) {
offset += l;
} else {
ok = false;
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
+
while (offset < end_offset && !is_shutting_down()) {
- if (!strm.is_writable()) {
+ if (!strm.wait_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
offset += l;
- if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
+ if (!write_data(strm, d, l)) { ok = false; }
}
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
+
data_sink.done = [&](void) { data_available = false; };
while (data_available && !is_shutting_down()) {
- if (!strm.is_writable()) {
+ if (!strm.wait_writable()) {
return false;
} else if (!content_provider(offset, 0, data_sink)) {
return false;
// Emit chunked response header and footer for each chunk
auto chunk =
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
- if (!strm.is_writable() ||
- !write_data(strm, chunk.data(), chunk.size())) {
- ok = false;
- }
+ if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
}
} else {
ok = false;
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
+
auto done_with_trailer = [&](const Headers *trailer) {
if (!ok) { return; }
if (!payload.empty()) {
// Emit chunked response header and footer for each chunk
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
- if (!strm.is_writable() ||
- !write_data(strm, chunk.data(), chunk.size())) {
+ if (!write_data(strm, chunk.data(), chunk.size())) {
ok = false;
return;
}
}
- static const std::string done_marker("0\r\n");
- if (!write_data(strm, done_marker.data(), done_marker.size())) {
- ok = false;
- }
+ constexpr const char done_marker[] = "0\r\n";
+ if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
// Trailer
if (trailer) {
}
}
- static const std::string crlf("\r\n");
- if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; }
+ constexpr const char crlf[] = "\r\n";
+ if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
};
data_sink.done = [&](void) { done_with_trailer(nullptr); };
};
while (data_available && !is_shutting_down()) {
- if (!strm.is_writable()) {
+ if (!strm.wait_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, 0, data_sink)) {
new_req.path = path;
new_req.redirect_count_ -= 1;
- if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
+ if (res.status == StatusCode::SeeOther_303 &&
+ (req.method != "GET" && req.method != "HEAD")) {
new_req.method = "GET";
new_req.body.clear();
new_req.headers.clear();
req = new_req;
res = new_res;
- if (res.location.empty()) res.location = location;
+ if (res.location.empty()) { res.location = location; }
}
return ret;
}
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)));
- }
+ if (!all_valid_ranges) { return; }
- 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
break;
}
- static const std::string header_name = "content-type:";
const auto header = buf_head(pos);
- if (start_with_case_ignore(header, header_name)) {
- file_.content_type = trim_copy(header.substr(header_name.size()));
+
+ if (!parse_header(header.data(), header.data() + header.size(),
+ [&](const std::string &, const std::string &) {})) {
+ is_valid_ = false;
+ return false;
+ }
+
+ constexpr const char header_content_type[] = "Content-Type:";
+
+ if (start_with_case_ignore(header, header_content_type)) {
+ file_.content_type =
+ trim_copy(header.substr(str_len(header_content_type)));
} else {
- static const std::regex re_content_disposition(
+ thread_local const std::regex re_content_disposition(
R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
std::regex_constants::icase);
it = params.find("filename*");
if (it != params.end()) {
- // Only allow UTF-8 enconnding...
- static const std::regex re_rfc5987_encoding(
+ // Only allow UTF-8 encoding...
+ thread_local const std::regex re_rfc5987_encoding(
R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
std::smatch m2;
return false;
}
}
- } else {
- is_valid_ = false;
- return false;
}
}
buf_erase(pos + crlf_.size());
file_.content_type.clear();
}
- bool start_with_case_ignore(const std::string &a,
- 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; }
+ bool start_with_case_ignore(const std::string &a, const char *b) const {
+ const auto b_len = strlen(b);
+ if (a.size() < b_len) { return false; }
+ for (size_t i = 0; i < b_len; i++) {
+ 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 make_multipart_data_boundary() {
- static const char data[] =
+inline std::string random_string(size_t length) {
+ constexpr const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- // std::random_device might actually be deterministic on some
- // platforms, but due to lack of support in the c++ standard library,
- // doing better requires either some ugly hacks or breaking portability.
- std::random_device seed_gen;
-
- // Request 128 bits of entropy for initialization
- std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
- std::mt19937 engine(seed_sequence);
-
- std::string result = "--cpp-httplib-multipart-data-";
+ thread_local auto engine([]() {
+ // std::random_device might actually be deterministic on some
+ // platforms, but due to lack of support in the c++ standard library,
+ // doing better requires either some ugly hacks or breaking portability.
+ std::random_device seed_gen;
+ // Request 128 bits of entropy for initialization
+ std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
+ return std::mt19937(seed_sequence);
+ }());
- for (auto i = 0; i < 16; i++) {
+ std::string result;
+ for (size_t i = 0; i < length; i++) {
result += data[engine() % (sizeof(data) - 1)];
}
-
return result;
}
+inline std::string make_multipart_data_boundary() {
+ return "--cpp-httplib-multipart-data-" + detail::random_string(16);
+}
+
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
auto valid = true;
for (size_t i = 0; i < boundary.size(); i++) {
body += item.content + serialize_multipart_formdata_item_end();
}
- if (finish) body += serialize_multipart_formdata_finish(boundary);
+ if (finish) { body += serialize_multipart_formdata_finish(boundary); }
return body;
}
-inline std::pair<size_t, size_t>
-get_range_offset_and_length(const Request &req, size_t content_length,
- size_t index) {
- auto r = req.ranges[index];
+inline bool range_error(Request &req, Response &res) {
+ if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
+ ssize_t content_len = static_cast<ssize_t>(
+ res.content_length_ ? res.content_length_ : res.body.size());
- if (r.first == -1 && r.second == -1) {
- return std::make_pair(0, content_length);
- }
+ ssize_t prev_first_pos = -1;
+ ssize_t prev_last_pos = -1;
+ size_t overwrapping_count = 0;
+
+ // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
+ // 'HTTP Semantics' to avoid potential denial-of-service attacks.
+ // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
+
+ // Too many ranges
+ if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
+
+ for (auto &r : req.ranges) {
+ auto &first_pos = r.first;
+ auto &last_pos = r.second;
+
+ if (first_pos == -1 && last_pos == -1) {
+ first_pos = 0;
+ last_pos = content_len;
+ }
+
+ if (first_pos == -1) {
+ first_pos = content_len - last_pos;
+ last_pos = content_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 >= content_len) {
+ last_pos = content_len - 1;
+ }
- auto slen = static_cast<ssize_t>(content_length);
+ // Range must be within content length
+ if (!(0 <= first_pos && first_pos <= last_pos &&
+ last_pos <= content_len - 1)) {
+ return true;
+ }
- if (r.first == -1) {
- r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
- r.second = slen - 1;
+ // Ranges must be in ascending order
+ if (first_pos <= prev_first_pos) { return true; }
+
+ // Request must not have more than two overlapping ranges
+ if (first_pos <= prev_last_pos) {
+ overwrapping_count++;
+ if (overwrapping_count > 2) { return true; }
+ }
+
+ prev_first_pos = (std::max)(prev_first_pos, first_pos);
+ prev_last_pos = (std::max)(prev_last_pos, last_pos);
+ }
}
- if (r.second == -1) { r.second = slen - 1; }
+ return false;
+}
+
+inline std::pair<size_t, size_t>
+get_range_offset_and_length(Range r, size_t content_length) {
+ assert(r.first != -1 && r.second != -1);
+ 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 std::string
-make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
- size_t content_length) {
+inline std::string make_content_range_header_field(
+ const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
+ auto st = offset_and_length.first;
+ auto ed = st + offset_and_length.second - 1;
+
std::string field = "bytes ";
- if (range.first != -1) { field += std::to_string(range.first); }
+ field += std::to_string(st);
field += "-";
- if (range.second != -1) { field += std::to_string(range.second); }
+ field += std::to_string(ed);
field += "/";
field += std::to_string(content_length);
return field;
}
template <typename SToken, typename CToken, typename Content>
-bool process_multipart_ranges_data(const Request &req, Response &res,
+bool process_multipart_ranges_data(const Request &req,
const std::string &boundary,
const std::string &content_type,
- SToken stoken, CToken ctoken,
- Content content) {
+ size_t content_length, SToken stoken,
+ CToken ctoken, Content content) {
for (size_t i = 0; i < req.ranges.size(); i++) {
ctoken("--");
stoken(boundary);
ctoken("\r\n");
}
+ auto offset_and_length =
+ get_range_offset_and_length(req.ranges[i], content_length);
+
ctoken("Content-Range: ");
- const auto &range = req.ranges[i];
- stoken(make_content_range_header_field(range, res.content_length_));
+ stoken(make_content_range_header_field(offset_and_length, content_length));
ctoken("\r\n");
ctoken("\r\n");
- auto offsets = get_range_offset_and_length(req, res.content_length_, i);
- auto offset = offsets.first;
- auto length = offsets.second;
- if (!content(offset, length)) { return false; }
+ if (!content(offset_and_length.first, offset_and_length.second)) {
+ return false;
+ }
ctoken("\r\n");
}
return true;
}
-inline bool make_multipart_ranges_data(const Request &req, Response &res,
+inline void make_multipart_ranges_data(const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type,
+ size_t content_length,
std::string &data) {
- return process_multipart_ranges_data(
- req, res, boundary, content_type,
+ process_multipart_ranges_data(
+ req, boundary, content_type, content_length,
[&](const std::string &token) { data += token; },
[&](const std::string &token) { data += token; },
[&](size_t offset, size_t length) {
- if (offset < res.body.size()) {
- data += res.body.substr(offset, length);
- return true;
- }
- return false;
+ assert(offset + length <= content_length);
+ data += res.body.substr(offset, length);
+ return true;
});
}
-inline size_t
-get_multipart_ranges_data_length(const Request &req, Response &res,
- const std::string &boundary,
- const std::string &content_type) {
+inline size_t get_multipart_ranges_data_length(const Request &req,
+ const std::string &boundary,
+ const std::string &content_type,
+ size_t content_length) {
size_t data_length = 0;
process_multipart_ranges_data(
- req, res, boundary, content_type,
+ req, boundary, content_type, content_length,
[&](const std::string &token) { data_length += token.size(); },
[&](const std::string &token) { data_length += token.size(); },
[&](size_t /*offset*/, size_t length) {
}
template <typename T>
-inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
- Response &res,
- const std::string &boundary,
- const std::string &content_type,
- const T &is_shutting_down) {
+inline bool
+write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
+ const std::string &boundary,
+ const std::string &content_type,
+ size_t content_length, const T &is_shutting_down) {
return process_multipart_ranges_data(
- req, res, boundary, content_type,
+ req, boundary, content_type, content_length,
[&](const std::string &token) { strm.write(token); },
[&](const std::string &token) { strm.write(token); },
[&](size_t offset, size_t length) {
});
}
-inline std::pair<size_t, size_t>
-get_range_offset_and_length(const Request &req, const Response &res,
- size_t index) {
- auto r = req.ranges[index];
-
- if (r.second == -1) {
- r.second = static_cast<ssize_t>(res.content_length_) - 1;
- }
-
- return std::make_pair(r.first, 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) {
auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
if (res.has_header(auth_key)) {
- static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
+ thread_local auto re =
+ std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
auto s = res.get_header_value(auth_key);
auto pos = s.find(' ');
if (pos != std::string::npos) {
return false;
}
-// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
-inline std::string random_string(size_t length) {
- auto randchar = []() -> char {
- const char charset[] = "0123456789"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz";
- const size_t max_index = (sizeof(charset) - 1);
- return charset[static_cast<size_t>(std::rand()) % max_index];
- };
- std::string str(length, 0);
- std::generate_n(str.begin(), length, randchar);
- return str;
-}
-
class ContentProviderAdapter {
public:
explicit ContentProviderAdapter(
#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,
const Params ¶ms) {
std::string path_with_query = path;
- const static std::regex re("[^?]+\\?.*");
+ thread_local const std::regex re("[^?]+\\?.*");
auto delm = std::regex_match(path, re) ? '&' : '?';
path_with_query += delm + detail::params_to_query_str(params);
return path_with_query;
}
// Header utilities
-inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+inline std::pair<std::string, std::string>
+make_range_header(const Ranges &ranges) {
std::string field = "bytes=";
auto i = 0;
- for (auto r : ranges) {
+ for (const auto &r : ranges) {
if (i != 0) { field += ", "; }
if (r.first != -1) { field += std::to_string(r.first); }
field += '-';
}
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;
} else {
- this->status = 302;
+ this->status = StatusCode::Found_302;
}
}
}
set_content(s.data(), s.size(), content_type);
}
+inline void Response::set_content(std::string &&s,
+ const std::string &content_type) {
+ body = std::move(s);
+
+ auto rng = headers.equal_range("Content-Type");
+ headers.erase(rng.first, rng.second);
+ set_header("Content-Type", content_type);
+}
+
inline void Response::set_content_provider(
size_t in_length, const std::string &content_type, ContentProvider provider,
ContentProviderResourceReleaser resource_releaser) {
set_header("Content-Type", content_type);
content_length_ = in_length;
if (in_length > 0) { content_provider_ = std::move(provider); }
- content_provider_resource_releaser_ = resource_releaser;
+ content_provider_resource_releaser_ = std::move(resource_releaser);
is_chunked_content_provider_ = false;
}
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
- content_provider_resource_releaser_ = resource_releaser;
+ content_provider_resource_releaser_ = std::move(resource_releaser);
is_chunked_content_provider_ = false;
}
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
- content_provider_resource_releaser_ = resource_releaser;
+ content_provider_resource_releaser_ = std::move(resource_releaser);
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() {}
+inline SocketStream::~SocketStream() = default;
inline bool SocketStream::is_readable() const {
- return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ return read_buff_off_ < read_buff_content_size_;
+}
+
+inline bool SocketStream::wait_readable() const {
+ 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 bool SocketStream::wait_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
is_socket_alive(sock_);
}
}
}
- if (!is_readable()) { return -1; }
+ if (!wait_readable()) { return -1; }
read_buff_off_ = 0;
read_buff_content_size_ = 0;
}
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
- if (!is_writable()) { return -1; }
+ if (!wait_writable()) { return -1; }
#if defined(_WIN32) && !defined(_WIN64)
size =
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 bool BufferStream::is_writable() const { return true; }
+inline bool BufferStream::wait_readable() const { return true; }
+
+inline bool BufferStream::wait_writable() const { return true; }
inline ssize_t BufferStream::read(char *ptr, size_t size) {
#if defined(_MSC_VER) && _MSC_VER < 1910
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) {
+ constexpr const 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 + str_len(marker);
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
#endif
}
-inline Server::~Server() {}
+inline Server::~Server() = default;
inline std::unique_ptr<detail::MatcherBase>
Server::make_matcher(const std::string &pattern) {
}
inline Server &Server::Get(const std::string &pattern, Handler handler) {
- get_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ get_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
inline Server &Server::Post(const std::string &pattern, Handler handler) {
- post_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ post_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
inline Server &Server::Post(const std::string &pattern,
HandlerWithContentReader handler) {
- post_handlers_for_content_reader_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ post_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
+ std::move(handler));
return *this;
}
inline Server &Server::Put(const std::string &pattern, Handler handler) {
- put_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ put_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
inline Server &Server::Put(const std::string &pattern,
HandlerWithContentReader handler) {
- put_handlers_for_content_reader_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ put_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
+ std::move(handler));
return *this;
}
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
- patch_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
inline Server &Server::Patch(const std::string &pattern,
HandlerWithContentReader handler) {
- patch_handlers_for_content_reader_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
+ std::move(handler));
return *this;
}
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
- delete_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
inline Server &Server::Delete(const std::string &pattern,
HandlerWithContentReader handler) {
- delete_handlers_for_content_reader_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
+ std::move(handler));
return *this;
}
inline Server &Server::Options(const std::string &pattern, Handler handler) {
- options_handlers_.push_back(
- std::make_pair(make_matcher(pattern), std::move(handler)));
+ options_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
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) {
- if (bind_internal(host, port, socket_flags) < 0) return false;
- return true;
+ auto ret = bind_internal(host, port, socket_flags);
+ if (ret == -1) { is_decommissioned = 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_decommissioned = 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_decommissioned) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
}
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
+ is_decommissioned = false;
}
-inline bool Server::parse_request_line(const char *s, Request &req) {
+inline void Server::decommission() { is_decommissioned = 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; }
len -= 2;
if (count != 3) { return false; }
}
- static const std::set<std::string> methods{
+ thread_local const std::set<std::string> methods{
"GET", "HEAD", "POST", "PUT", "DELETE",
"CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
}
}
- size_t count = 0;
-
- detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
- [&](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;
}
inline bool Server::write_response(Stream &strm, bool close_connection,
- const Request &req, Response &res) {
+ Request &req, Response &res) {
+ // NOTE: `req.ranges` should be empty, otherwise it will be applied
+ // incorrectly to the error content.
+ req.ranges.clear();
return write_response_core(strm, close_connection, req, res, false);
}
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");
}
// 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;
- }
-
+ if (!detail::write_response_line(bstrm, res.status)) { return false; }
if (!header_writer_(bstrm, res.headers)) { return false; }
// Flush buffer
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
res.content_provider_success_ = true;
} else {
- res.content_provider_success_ = false;
ret = false;
}
}
return detail::write_content(strm, res.content_provider_, 0,
res.content_length_, is_shutting_down);
} else if (req.ranges.size() == 1) {
- auto offsets =
- detail::get_range_offset_and_length(req, res.content_length_, 0);
- auto offset = offsets.first;
- auto length = offsets.second;
- return detail::write_content(strm, res.content_provider_, offset, length,
- is_shutting_down);
+ auto offset_and_length = detail::get_range_offset_and_length(
+ req.ranges[0], res.content_length_);
+
+ return detail::write_content(strm, res.content_provider_,
+ offset_and_length.first,
+ offset_and_length.second, is_shutting_down);
} else {
return detail::write_multipart_ranges_data(
- strm, req, res, boundary, content_type, is_shutting_down);
+ strm, req, res, boundary, content_type, res.content_length_,
+ is_shutting_down);
}
} else {
if (res.is_chunked_content_provider_) {
} else if (type == detail::EncodingType::Brotli) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
+#endif
+ } else if (type == detail::EncodingType::Zstd) {
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+ compressor = detail::make_unique<detail::zstd_compressor>();
#endif
} else {
compressor = detail::make_unique<detail::nocompressor>();
const auto &content_type = req.get_header_value("Content-Type");
if (!content_type.find("application/x-www-form-urlencoded")) {
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
- res.status = 413; // NOTE: should be 414?
+ res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
return false;
}
detail::parse_query_text(req.body, req.params);
std::move(multipart_receiver));
}
-inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
- ContentReceiver receiver,
- MultipartContentHeader multipart_header,
- ContentReceiver multipart_receiver) {
+inline bool
+Server::read_content_core(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver) const {
detail::MultipartFormDataParser multipart_form_data_parser;
ContentReceiverWithProgress out;
const auto &content_type = req.get_header_value("Content-Type");
std::string boundary;
if (!detail::parse_multipart_boundary(content_type, boundary)) {
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
return false;
}
if (req.is_multipart_form_data()) {
if (!multipart_form_data_parser.is_valid()) {
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
return false;
}
}
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.c_str(), kv.second);
+ res.set_header(kv.first, kv.second);
}
auto mm = std::make_shared<detail::mmap>(path.c_str());
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_decommissioned) { 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_decommissioned) { return false; }
+
auto ret = true;
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
#ifndef _WIN32
}
#endif
+
+#if defined _WIN32
+ // sockets connected 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;
break;
}
- {
-#ifdef _WIN32
- auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
- read_timeout_usec_ / 1000);
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
- reinterpret_cast<const char *>(&timeout), sizeof(timeout));
-#else
- timeval tv;
- tv.tv_sec = static_cast<long>(read_timeout_sec_);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
- reinterpret_cast<const void *>(&tv), sizeof(tv));
-#endif
- }
- {
+ detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
+ read_timeout_sec_, read_timeout_usec_);
+ detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
+ write_timeout_sec_, write_timeout_usec_);
-#ifdef _WIN32
- auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
- write_timeout_usec_ / 1000);
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
- reinterpret_cast<const char *>(&timeout), sizeof(timeout));
-#else
- timeval tv;
- tv.tv_sec = static_cast<long>(write_timeout_sec_);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
- reinterpret_cast<const void *>(&tv), sizeof(tv));
-#endif
+ if (!task_queue->enqueue(
+ [this, sock]() { process_and_close_socket(sock); })) {
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
}
-
- task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
}
task_queue->shutdown();
}
+ is_decommissioned = !ret;
return ret;
}
return dispatch_request(req, res, patch_handlers_);
}
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
return false;
}
inline bool Server::dispatch_request(Request &req, Response &res,
- const Handlers &handlers) {
+ const Handlers &handlers) const {
for (const auto &x : handlers) {
const auto &matcher = x.first;
const auto &handler = x.second;
inline void Server::apply_ranges(const Request &req, Response &res,
std::string &content_type,
- std::string &boundary) {
- if (req.ranges.size() > 1) {
- boundary = detail::make_multipart_data_boundary();
-
+ std::string &boundary) const {
+ 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;
res.headers.erase(it);
}
+ boundary = detail::make_multipart_data_boundary();
+
res.set_header("Content-Type",
"multipart/byteranges; boundary=" + boundary);
}
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 offsets =
- detail::get_range_offset_and_length(req, res.content_length_, 0);
- length = offsets.second;
+ auto offset_and_length = detail::get_range_offset_and_length(
+ req.ranges[0], res.content_length_);
+
+ length = offset_and_length.second;
auto content_range = detail::make_content_range_header_field(
- req.ranges[0], res.content_length_);
+ offset_and_length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
- length = detail::get_multipart_ranges_data_length(req, res, boundary,
- content_type);
+ length = detail::get_multipart_ranges_data_length(
+ req, boundary, content_type, res.content_length_);
}
res.set_header("Content-Length", std::to_string(length));
} else {
res.set_header("Content-Encoding", "gzip");
} else if (type == detail::EncodingType::Brotli) {
res.set_header("Content-Encoding", "br");
+ } else if (type == detail::EncodingType::Zstd) {
+ res.set_header("Content-Encoding", "zstd");
}
}
}
}
} else {
- if (req.ranges.empty()) {
+ if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
;
} else if (req.ranges.size() == 1) {
+ auto offset_and_length =
+ detail::get_range_offset_and_length(req.ranges[0], res.body.size());
+ auto offset = offset_and_length.first;
+ auto length = offset_and_length.second;
+
auto content_range = detail::make_content_range_header_field(
- req.ranges[0], res.body.size());
+ offset_and_length, res.body.size());
res.set_header("Content-Range", content_range);
- auto offsets =
- detail::get_range_offset_and_length(req, res.body.size(), 0);
- auto offset = offsets.first;
- auto length = offsets.second;
-
- if (offset < res.body.size()) {
- res.body = res.body.substr(offset, length);
- } else {
- res.body.clear();
- res.status = 416;
- }
+ assert(offset + length <= res.body.size());
+ res.body = res.body.substr(offset, length);
} else {
std::string data;
- if (detail::make_multipart_ranges_data(req, res, boundary, content_type,
- data)) {
- res.body.swap(data);
- } else {
- res.body.clear();
- res.status = 416;
- }
+ detail::make_multipart_ranges_data(req, res, boundary, content_type,
+ res.body.size(), data);
+ res.body.swap(data);
}
if (type != detail::EncodingType::None) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
content_encoding = "br";
+#endif
+ } else if (type == detail::EncodingType::Zstd) {
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+ compressor = detail::make_unique<detail::zstd_compressor>();
+ content_encoding = "zstd";
#endif
}
inline bool Server::dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
- const HandlersForContentReader &handlers) {
+ const HandlersForContentReader &handlers) const {
for (const auto &x : handlers) {
const auto &matcher = x.first;
const auto &handler = x.second;
}
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{};
res.version = "HTTP/1.1";
res.headers = default_headers_;
-#ifdef _WIN32
- // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
-#else
-#ifndef CPPHTTPLIB_USE_POLL
- // Socket file descriptor exceeded FD_SETSIZE...
- if (strm.socket() >= FD_SETSIZE) {
- Headers dummy;
- detail::read_headers(strm, dummy);
- res.status = 500;
- return write_response(strm, close_connection, req, res);
- }
-#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 = 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)) {
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
+ 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);
}
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));
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
- res.status = 416;
+ res.status = StatusCode::RangeNotSatisfiable_416;
return write_response(strm, close_connection, req, res);
}
}
if (setup_request) { setup_request(req); }
if (req.get_header_value("Expect") == "100-continue") {
- auto status = 100;
+ int status = StatusCode::Continue_100;
if (expect_100_continue_handler_) {
status = expect_100_continue_handler_(req, res);
}
switch (status) {
- case 100:
- case 417:
- strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
- status_message(status));
+ case StatusCode::Continue_100:
+ case StatusCode::ExpectationFailed_417:
+ 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);
}
}
- // Rounting
+ // Setup `is_connection_closed` method
+ req.is_connection_closed = [&]() {
+ return !detail::is_socket_alive(strm.socket());
+ };
+
+ // Routing
auto routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
routed = routing(req, res, strm);
exception_handler_(req, res, ep);
routed = true;
} else {
- res.status = 500;
+ res.status = StatusCode::InternalServerError_500;
std::string val;
auto s = e.what();
for (size_t i = 0; s[i]; i++) {
exception_handler_(req, res, ep);
routed = true;
} else {
- res.status = 500;
+ res.status = StatusCode::InternalServerError_500;
res.set_header("EXCEPTION_WHAT", "UNKNOWN");
}
}
#endif
-
if (routed) {
- if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
+ if (res.status == -1) {
+ res.status = req.ranges.empty() ? StatusCode::OK_200
+ : 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;
+ res.content_provider_ = nullptr;
+ res.status = StatusCode::RangeNotSatisfiable_416;
+ return write_response(strm, close_connection, req, res);
+ }
+
return write_response_with_content(strm, close_connection, req, res);
} else {
- if (res.status == -1) { res.status = 404; }
+ if (res.status == -1) { res.status = StatusCode::NotFound_404; }
+
return write_response(strm, close_connection, req, res);
}
}
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() {
+ // Wait until all the requests in flight are handled.
+ size_t retry_count = 10;
+ while (retry_count-- > 0) {
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ if (socket_requests_in_flight_ == 0) { break; }
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds{1});
+ }
+
std::lock_guard<std::mutex> guard(socket_mutex_);
shutdown_socket(socket_);
close_socket(socket_);
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_
std::string ip;
auto it = addr_map_.find(host_);
- if (it != addr_map_.end()) ip = it->second;
+ 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,
socket_requests_are_from_thread_ == std::this_thread::get_id());
}
-inline void ClientImpl::shutdown_socket(Socket &socket) {
+inline void ClientImpl::shutdown_socket(Socket &socket) const {
if (socket.sock == INVALID_SOCKET) { return; }
detail::shutdown_socket(socket.sock);
}
}
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
- Response &res) {
+ Response &res) const {
std::array<char, 2048> buf{};
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
if (!line_reader.getline()) { return false; }
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
- const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
+ thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
#else
- const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
+ thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
#endif
std::cmatch m;
res.reason = std::string(m[3]);
// Ignore '100 Continue'
- while (res.status == 100) {
+ while (res.status == StatusCode::Continue_100) {
if (!line_reader.getline()) { return false; } // CRLF
if (!line_reader.getline()) { return false; } // next response line
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
+ // Attempt to avoid sigpipe by shutting down non-gracefully if it seems
// like the other side has already closed the connection Also, there
// cannot be any requests in flight from other threads since we locked
// request_mutex_, so safe to close everything immediately
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);
});
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if ((res.status == 401 || res.status == 407) &&
+ if ((res.status == StatusCode::Unauthorized_401 ||
+ res.status == StatusCode::ProxyAuthenticationRequired_407) &&
req.authorization_count_ < 5) {
- auto is_proxy = res.status == 407;
+ auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
const auto &username =
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
const auto &password =
auto location = res.get_header_value("location");
if (location.empty()) { return false; }
- const static std::regex re(
- R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
+ thread_local const std::regex re(
+ R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
std::smatch m;
if (!std::regex_match(location, m, re)) { return false; }
} else {
if (next_scheme == "https") {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- SSLClient cli(next_host.c_str(), next_port);
+ SSLClient cli(next_host, next_port);
cli.copy_settings(*this);
if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
return detail::redirect(cli, req, res, path, location, error);
return false;
#endif
} else {
- ClientImpl cli(next_host.c_str(), next_port);
+ ClientImpl cli(next_host, next_port);
cli.copy_settings(*this);
return detail::redirect(cli, req, res, path, location, error);
}
inline bool ClientImpl::write_content_with_provider(Stream &strm,
const Request &req,
- Error &error) {
+ Error &error) const {
auto is_shutting_down = []() { return false; };
if (req.is_chunked_content_provider_) {
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
+#ifdef CPPHTTPLIB_ZSTD_SUPPORT
+ if (!accept_encoding.empty()) { accept_encoding += ", "; }
+ accept_encoding += "zstd";
+#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 != 204) && req.method != "HEAD" && req.method != "CONNECT") {
- auto redirect = 300 < res.status && res.status < 400 && follow_location_;
+ if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
+ req.method != "CONNECT") {
+ 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 ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
- const MultipartFormDataProviderItems &provider_items) {
- size_t cur_item = 0, cur_start = 0;
+ const MultipartFormDataProviderItems &provider_items) const {
+ size_t cur_item = 0;
+ size_t cur_start = 0;
// cur_item and cur_start are copied to within the std::function and maintain
// state between successive calls
return [&, cur_item, cur_start](size_t offset,
DataSink &sink) mutable -> bool {
- if (!offset && items.size()) {
+ if (!offset && !items.empty()) {
sink.os << detail::serialize_multipart_formdata(items, boundary, false);
return true;
} else if (cur_item < provider_items.size()) {
cur_sink.write = sink.write;
cur_sink.done = [&]() { has_data = false; };
- if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
+ if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
return false;
+ }
if (!has_data) {
sink.os << detail::serialize_multipart_formdata_item_end();
};
}
-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));
}
if (params.empty()) { return Get(path, headers); }
std::string path_with_query = append_query_params(path, params);
- return Get(path_with_query.c_str(), headers, progress);
+ return Get(path_with_query, headers, std::move(progress));
}
inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
const Headers &headers,
ContentReceiver content_receiver,
Progress progress) {
- return Get(path, params, headers, nullptr, content_receiver, progress);
+ return Get(path, params, headers, nullptr, std::move(content_receiver),
+ std::move(progress));
}
inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
ContentReceiver content_receiver,
Progress progress) {
if (params.empty()) {
- return Get(path, headers, response_handler, content_receiver, progress);
+ return Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
}
std::string path_with_query = append_query_params(path, params);
- return Get(path_with_query.c_str(), headers, response_handler,
- content_receiver, progress);
+ return Get(path_with_query, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
}
inline Result ClientImpl::Head(const std::string &path) {
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);
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
- return Post(path, headers, body, content_type.c_str());
+ return Post(path, headers, body, content_type);
}
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
- return Post(path, headers, body, content_type.c_str());
+ return Post(path, headers, body, content_type);
}
inline Result
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) {
+ std::size_t size) const {
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
- if (!mem) return nullptr;
+ 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<SSLVerifierResponse(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) {
+ (void)(sock);
+ // SSL_shutdown() returns 0 on first call (indicating close_notify alert
+ // sent) and 1 on subsequent call (indicating close_notify alert received)
+ if (SSL_shutdown(ssl) == 0) {
+ // Expected to return 1, but even if it doesn't, we free ssl
+ SSL_shutdown(ssl);
+ }
+ }
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);
}
-class SSLInit {
-public:
- SSLInit() {
- OPENSSL_init_ssl(
- OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
- }
-};
-
// 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() {}
+inline SSLSocketStream::~SSLSocketStream() = default;
inline bool SSLSocketStream::is_readable() const {
- return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ return SSL_pending(ssl_) > 0;
}
-inline bool SSLSocketStream::is_writable() const {
+inline bool SSLSocketStream::wait_readable() const {
+ 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::wait_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()) {
+ } else if (wait_readable()) {
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret < 0) {
auto err = SSL_get_error(ssl_, ret);
#endif
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));
+ } else if (wait_readable()) {
+ 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) {
- if (is_writable()) {
+ if (wait_writable()) {
auto handle_size = static_cast<int>(
std::min<size_t>(size, (std::numeric_limits<int>::max)()));
#else
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
#endif
- if (is_writable()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ if (wait_writable()) {
+ 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_; }
-static SSLInit sslinit_;
+inline time_t SSLSocketStream::duration() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time)
+ .count();
+}
} // 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(std::string(b, 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());
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
- host_components_.emplace_back(std::string(b, e));
+ host_components_.emplace_back(b, e);
});
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
return false;
}
- if (proxy_res.status == 407) {
+ if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
if (!proxy_digest_auth_username_.empty() &&
!proxy_digest_auth_password_.empty()) {
std::map<std::string, std::string> auth;
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 status code is not 200, proxy request is failed.
// Set error to ProxyConnection and return proxy response
// as the response of the request
- if (proxy_res.status != 200) {
+ if (proxy_res.status != StatusCode::OK_200) {
error = Error::ProxyConnection;
res = std::move(proxy_res);
// Thread-safe to close everything because we are assuming there are
}
if (server_certificate_verification_) {
- verify_result_ = SSL_get_verify_result(ssl2);
+ auto verification_status = SSLVerifierResponse::NoDecisionMade;
- if (verify_result_ != X509_V_OK) {
- error = Error::SSLServerVerification;
- return false;
+ if (server_certificate_verifier_) {
+ verification_status = server_certificate_verifier_(ssl2);
}
- auto server_cert = SSL_get1_peer_certificate(ssl2);
-
- if (server_cert == nullptr) {
+ if (verification_status == SSLVerifierResponse::CertificateRejected) {
error = Error::SSLServerVerification;
return false;
}
- if (!verify_host(server_cert)) {
- X509_free(server_cert);
- error = Error::SSLServerVerification;
- return false;
+ if (verification_status == SSLVerifierResponse::NoDecisionMade) {
+ verify_result_ = SSL_get_verify_result(ssl2);
+
+ if (verify_result_ != X509_V_OK) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
+
+ 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_hostname_verification_) {
+ if (!verify_host(server_cert)) {
+ error = Error::SSLServerHostnameVerification;
+ return false;
+ }
+ }
}
- X509_free(server_cert);
}
return true;
},
[&](SSL *ssl2) {
- // NOTE: With -Wold-style-cast, this can produce a warning, since
- // SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
- // an old style cast. Short of doing compiler specific pragma's
- // here, we can't get rid of this warning. :'(
+#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_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__
std::vector<std::string> pattern_components;
detail::split(&pattern[0], &pattern[pattern_len], '.',
[&](const char *b, const char *e) {
- pattern_components.emplace_back(std::string(b, e));
+ pattern_components.emplace_back(b, e);
});
if (host_components_.size() != pattern_components.size()) { return false; }
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)) {}
: cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
client_key_path)) {}
-inline Client::~Client() {}
+inline Client::~Client() = default;
inline bool Client::is_valid() const {
return cli_ != nullptr && cli_->is_valid();
}
inline Result Client::Get(const std::string &path, const Params ¶ms,
const Headers &headers, Progress progress) {
- return cli_->Get(path, params, headers, progress);
+ return cli_->Get(path, params, headers, std::move(progress));
}
inline Result Client::Get(const std::string &path, const Params ¶ms,
const Headers &headers,
ContentReceiver content_receiver, Progress progress) {
- return cli_->Get(path, params, headers, content_receiver, progress);
+ return cli_->Get(path, params, headers, std::move(content_receiver),
+ std::move(progress));
}
inline Result Client::Get(const std::string &path, const Params ¶ms,
const Headers &headers,
ResponseHandler response_handler,
ContentReceiver content_receiver, Progress progress) {
- return cli_->Get(path, params, headers, response_handler, content_receiver,
- progress);
+ return cli_->Get(path, params, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
}
inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
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<SSLVerifierResponse(SSL *ssl)> verifier) {
+ cli_->set_server_certificate_verifier(verifier);
+}
#endif
inline void Client::set_logger(Logger logger) {
} // namespace httplib
-#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
-#undef poll
-#endif
-
-#endif // CPPHTTPLIB_HTTPLIB_H
+#endif // CPPHTTPLIB_HTTPLIB_H
\ No newline at end of file