//
// httplib.h
//
-// Copyright (c) 2024 Yuji Hirose. All rights reserved.
+// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
-#define CPPHTTPLIB_VERSION "0.18.5"
+#define CPPHTTPLIB_VERSION "0.19.0"
/*
* Configuration
#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_IDLE_INTERVAL_SECOND 0
#endif
#endif
using socket_t = SOCKET;
+using socklen_t = int;
#ifdef CPPHTTPLIB_USE_POLL
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
#endif
#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
+#ifndef __VMS
#include <sys/select.h>
+#endif
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
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 {
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
virtual socket_t socket() const = 0;
+ virtual time_t duration() const = 0;
+
ssize_t write(const char *ptr);
ssize_t write(const std::string &s);
};
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);
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
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_;
bool send_(Request &req, Response &res, Error &error);
Result send_(Request &&req);
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- bool is_ssl_peer_could_be_closed(SSL *ssl) const;
-#endif
socket_t create_client_socket(Error &error) const;
bool read_response_line(Stream &strm, const Request &req,
Response &res) 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;
};
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 shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
- bool process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback) override;
+ bool
+ process_socket(const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback) override;
bool is_ssl() const override;
- bool connect_with_proxy(Socket &sock, Response &res, bool &success,
- Error &error);
+ bool connect_with_proxy(
+ Socket &sock,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ Response &res, bool &success, Error &error);
bool initialize_ssl(Socket &socket, Error &error);
bool load_certs();
return detail::get_header_value_u64(headers, key, def, id);
}
-inline void default_socket_options(socket_t sock) {
- int opt = 1;
+namespace detail {
+
+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
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
- reinterpret_cast<const char *>(&opt), sizeof(opt));
+ reinterpret_cast<const char *>(optval),
#else
-#ifdef SO_REUSEPORT
- setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
- reinterpret_cast<const void *>(&opt), sizeof(opt));
+ optval,
+#endif
+ optlen) == 0;
+}
+
+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 bool set_socket_opt_time(socket_t sock, int level, int optname,
+ time_t sec, time_t usec) {
+#ifdef _WIN32
+ auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
#else
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
- reinterpret_cast<const void *>(&opt), sizeof(opt));
+ 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
+ SO_REUSEPORT,
+#else
+ SO_REUSEADDR,
#endif
+ 1);
}
inline const char *status_message(int status) {
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.
void split(const char *b, const char *e, char d, size_t m,
std::function<void(const char *, const char *)> fn);
-bool process_client_socket(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec,
- std::function<bool(Stream &)> callback);
+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,
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;
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 false; }
+ if (s.empty()) { return true; }
if (s.size() == 1) {
return is_field_vchar(s[0]);
});
}
-inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
+template <bool Read>
+inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
- struct pollfd pfd_read;
- pfd_read.fd = sock;
- pfd_read.events = POLLIN;
+ struct pollfd pfd;
+ pfd.fd = sock;
+ pfd.events = (Read ? POLLIN : POLLOUT);
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+ return handle_EINTR([&]() { return poll(&pfd, 1, timeout); });
#else
#ifndef _WIN32
if (sock >= FD_SETSIZE) { return -1; }
#endif
- fd_set fds;
+ fd_set fds, *rfds, *wfds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
+ rfds = (Read ? &fds : nullptr);
+ wfds = (Read ? nullptr : &fds);
timeval tv;
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
return handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
});
#endif
}
-inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
-#ifdef CPPHTTPLIB_USE_POLL
- struct pollfd pfd_read;
- pfd_read.fd = sock;
- pfd_read.events = POLLOUT;
-
- auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
-
- return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
-#else
-#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return -1; }
-#endif
-
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(sock, &fds);
-
- timeval tv;
- tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
+ return select_impl<true>(sock, sec, usec);
+}
- return handle_EINTR([&]() {
- return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
- });
-#endif
+inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
+ return select_impl<false>(sock, sec, usec);
}
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
class SocketStream final : public Stream {
public:
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
- time_t write_timeout_sec, time_t write_timeout_usec);
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec = 0,
+ std::chrono::time_point<std::chrono::steady_clock> start_time =
+ std::chrono::steady_clock::time_point::min());
~SocketStream() override;
bool is_readable() const override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
+ time_t duration() const override;
private:
socket_t sock_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
+ time_t max_timeout_msec_;
+ const std::chrono::time_point<std::chrono::steady_clock> start_time;
std::vector<char> read_buff_;
size_t read_buff_off_ = 0;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLSocketStream final : public Stream {
public:
- SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec);
+ SSLSocketStream(
+ socket_t sock, SSL *ssl, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, time_t max_timeout_msec = 0,
+ std::chrono::time_point<std::chrono::steady_clock> start_time =
+ std::chrono::steady_clock::time_point::min());
~SSLSocketStream() override;
bool is_readable() const override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
+ time_t duration() const override;
private:
socket_t sock_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
+ time_t max_timeout_msec_;
+ const std::chrono::time_point<std::chrono::steady_clock> start_time;
};
#endif
});
}
-inline bool 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
- if (tcp_nodelay) {
- auto opt = 1;
-#ifdef _WIN32
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const char *>(&opt), sizeof(opt));
-#else
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
- reinterpret_cast<const void *>(&opt), sizeof(opt));
-#endif
- }
+ if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
if (rp->ai_family == AF_INET6) {
- auto opt = ipv6_v6only ? 1 : 0;
-#ifdef _WIN32
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const char *>(&opt), sizeof(opt));
-#else
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
- reinterpret_cast<const void *>(&opt), sizeof(opt));
-#endif
+ set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
}
if (socket_options) { socket_options(sock); }
}
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;
if (!key_len) { return false; }
auto key = std::string(beg, key_end);
- auto val = case_ignore::equal(key, "Location")
- ? std::string(p, end)
- : decode_url(std::string(p, end), false);
-
- // NOTE: From RFC 9110:
- // Field values containing CR, LF, or NUL characters are
- // invalid and dangerous, due to the varying ways that
- // implementations might parse and interpret those
- // characters; a recipient of CR, LF, or NUL within a field
- // value MUST either reject the message or replace each of
- // those characters with SP before further processing or
- // forwarding of that message.
- static const std::string CR_LF_NUL("\r\n\0", 3);
- if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; }
-
- fn(key, val);
+ // auto val = (case_ignore::equal(key, "Location") ||
+ // case_ignore::equal(key, "Referer"))
+ // ? std::string(p, end)
+ // : decode_url(std::string(p, end), false);
+ auto val = std::string(p, end);
+
+ if (!detail::fields::is_field_value(val)) { return false; }
+
+ if (case_ignore::equal(key, "Location") ||
+ case_ignore::equal(key, "Referer")) {
+ fn(key, val);
+ } else {
+ fn(key, decode_url(val, false));
+ }
+
return true;
}
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
if (!parse_header(line_reader.ptr(), end,
- [&](const std::string &key, std::string &val) {
+ [&](const std::string &key, const std::string &val) {
headers.emplace(key, val);
})) {
return false;
uint64_t r = 0;
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
- if (n <= 0) { return true; }
+ if (n <= 0) { return false; }
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
r += static_cast<uint64_t>(n);
ret = read_content_without_length(strm, out);
} else {
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);
+ 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;
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;
+ }
+ if (req.has_header("Content-Length") &&
+ req.get_header_value_u64("Content-Length") > 0) {
return true;
}
- // TODO: check if Content-Length is set
+ 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) {
namespace detail {
+inline void calc_actual_timeout(time_t max_timeout_msec,
+ time_t duration_msec, time_t timeout_sec,
+ time_t timeout_usec, time_t &actual_timeout_sec,
+ time_t &actual_timeout_usec) {
+ auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
+
+ auto actual_timeout_msec =
+ std::min(max_timeout_msec - duration_msec, timeout_msec);
+
+ actual_timeout_sec = actual_timeout_msec / 1000;
+ actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
+}
+
// Socket stream implementation
-inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec,
- time_t write_timeout_sec,
- time_t write_timeout_usec)
+inline SocketStream::SocketStream(
+ socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time)
: sock_(sock), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
- write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
+ write_timeout_usec_(write_timeout_usec),
+ max_timeout_msec_(max_timeout_msec), start_time(start_time),
+ read_buff_(read_buff_size_, 0) {}
inline SocketStream::~SocketStream() = default;
inline bool SocketStream::is_readable() const {
- return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ if (max_timeout_msec_ <= 0) {
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ }
+
+ time_t read_timeout_sec;
+ time_t read_timeout_usec;
+ calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
+ read_timeout_usec_, read_timeout_sec, read_timeout_usec);
+
+ return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
}
inline bool SocketStream::is_writable() const {
inline socket_t SocketStream::socket() const { return sock_; }
+inline time_t SocketStream::duration() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time)
+ .count();
+}
+
// Buffer stream implementation
inline bool BufferStream::is_readable() const { return true; }
inline socket_t BufferStream::socket() const { return 0; }
+inline time_t BufferStream::duration() const { return 0; }
+
inline const std::string &BufferStream::get_buffer() const { return buffer; }
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
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
- }
- {
-
-#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
- }
+ 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_);
if (!task_queue->enqueue(
[this, sock]() { process_and_close_socket(sock); })) {
#endif
#endif
- // Check if the request URI doesn't exceed the limit
- if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
- Headers dummy;
- detail::read_headers(strm, dummy);
- res.status = StatusCode::UriTooLong_414;
- return write_response(strm, close_connection, req, res);
- }
-
// Request line and headers
if (!parse_request_line(line_reader.ptr(), req) ||
!detail::read_headers(strm, req.headers)) {
return write_response(strm, close_connection, req, res);
}
+ // Check if the request URI doesn't exceed the limit
+ if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+ Headers dummy;
+ detail::read_headers(strm, dummy);
+ res.status = StatusCode::UriTooLong_414;
+ return write_response(strm, close_connection, req, res);
+ }
+
if (req.get_header_value("Connection") == "close") {
connection_closed = true;
}
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_;
return ret;
}
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const {
- detail::set_nonblocking(socket_.sock, true);
- auto se = detail::scope_exit(
- [&]() { detail::set_nonblocking(socket_.sock, false); });
-
- char buf[1];
- return !SSL_peek(ssl, buf, 1) &&
- SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
-}
-#endif
-
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
{
std::lock_guard<std::mutex> guard(socket_mutex_);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if (is_alive && is_ssl()) {
- if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
+ if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
+ is_alive = false;
+ }
}
#endif
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);
});
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) {
- if (is_ssl_peer_could_be_closed(socket_.ssl)) {
+ if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
error = Error::SSLPeerCouldBeClosed_;
return false;
}
};
}
-inline bool
-ClientImpl::process_socket(const Socket &socket,
- std::function<bool(Stream &strm)> callback) {
+inline bool ClientImpl::process_socket(
+ const Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ std::function<bool(Stream &strm)> callback) {
return detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
- write_timeout_usec_, std::move(callback));
+ write_timeout_usec_, max_timeout_msec_, start_time,
+ std::move(callback));
}
inline bool ClientImpl::is_ssl() const { return false; }
req.path = path;
req.headers = headers;
req.progress = std::move(progress);
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
return content_receiver(data, data_length);
};
req.progress = std::move(progress);
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
req.method = "HEAD";
req.headers = headers;
req.path = path;
+ if (max_timeout_msec_ > 0) {
+ req.start_time_ = std::chrono::steady_clock::now();
+ }
return send_(std::move(req));
}
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);
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;
(void)(sock);
SSL_shutdown(ssl);
#else
- timeval tv;
- tv.tv_sec = 1;
- tv.tv_usec = 0;
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
- reinterpret_cast<const void *>(&tv), sizeof(tv));
+ detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, 1, 0);
auto ret = SSL_shutdown(ssl);
while (ret == 0) {
}
template <typename T>
-inline bool
-process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec, time_t write_timeout_sec,
- time_t write_timeout_usec, T callback) {
+inline bool process_client_socket_ssl(
+ SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
- write_timeout_sec, write_timeout_usec);
+ write_timeout_sec, write_timeout_usec,
+ max_timeout_msec, start_time);
return callback(strm);
}
};
// SSL socket stream implementation
-inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
- time_t read_timeout_sec,
- time_t read_timeout_usec,
- time_t write_timeout_sec,
- time_t write_timeout_usec)
+inline SSLSocketStream::SSLSocketStream(
+ socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ time_t max_timeout_msec,
+ std::chrono::time_point<std::chrono::steady_clock> start_time)
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
- write_timeout_usec_(write_timeout_usec) {
+ write_timeout_usec_(write_timeout_usec),
+ max_timeout_msec_(max_timeout_msec), start_time(start_time) {
SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
}
inline SSLSocketStream::~SSLSocketStream() = default;
inline bool SSLSocketStream::is_readable() const {
- return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ if (max_timeout_msec_ <= 0) {
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ }
+
+ time_t read_timeout_sec;
+ time_t read_timeout_usec;
+ calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
+ read_timeout_usec_, read_timeout_sec, read_timeout_usec);
+
+ return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
}
inline bool SSLSocketStream::is_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
- is_socket_alive(sock_);
+ is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
}
}
return ret;
+ } else {
+ return -1;
}
- return -1;
}
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
inline socket_t SSLSocketStream::socket() const { return sock_; }
+inline time_t SSLSocketStream::duration() const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time)
+ .count();
+}
+
static SSLInit sslinit_;
} // namespace detail
}
// Assumes that socket_mutex_ is locked and that there are no requests in flight
-inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
- bool &success, Error &error) {
+inline bool SSLClient::connect_with_proxy(
+ Socket &socket,
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
+ Response &res, bool &success, Error &error) {
success = true;
Response proxy_res;
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
- write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
+ start_time, [&](Stream &strm) {
Request req2;
req2.method = "CONNECT";
req2.path = host_and_port_;
+ if (max_timeout_msec_ > 0) {
+ req2.start_time_ = std::chrono::steady_clock::now();
+ }
return process_request(strm, req2, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are no
proxy_res = Response();
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
- write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
+ start_time, [&](Stream &strm) {
Request req3;
req3.method = "CONNECT";
req3.path = host_and_port_;
req3, auth, 1, detail::random_string(10),
proxy_digest_auth_username_, proxy_digest_auth_password_,
true));
+ if (max_timeout_msec_ > 0) {
+ req3.start_time_ = std::chrono::steady_clock::now();
+ }
return process_request(strm, req3, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are
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; }