summaryrefslogtreecommitdiff
path: root/examples/server/httplib.h
diff options
context:
space:
mode:
authorXuan Son Nguyen <thichthat@gmail.com>2024-03-20 13:30:36 +0100
committerGitHub <noreply@github.com>2024-03-20 13:30:36 +0100
commit91f8ad167dcd24b54615b468d9dd764ebe1d37ad (patch)
tree3455dcd133108339d9b47e9183d08dab901bf407 /examples/server/httplib.h
parent6b7e76d28cdf4361740054140708c71828453522 (diff)
Server: version bump for httplib and json (#6169)
* server: version bump for httplib and json * fix build * bring back content_length
Diffstat (limited to 'examples/server/httplib.h')
-rw-r--r--examples/server/httplib.h1797
1 files changed, 1234 insertions, 563 deletions
diff --git a/examples/server/httplib.h b/examples/server/httplib.h
index 28746000..f360bd93 100644
--- a/examples/server/httplib.h
+++ b/examples/server/httplib.h
@@ -1,14 +1,14 @@
//
// httplib.h
//
-// Copyright (c) 2023 Yuji Hirose. All rights reserved.
+// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
-#define CPPHTTPLIB_VERSION "0.12.2"
+#define CPPHTTPLIB_VERSION "0.15.3"
/*
* Configuration
@@ -82,6 +82,10 @@
#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
@@ -160,10 +164,6 @@ using ssize_t = long;
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
#endif
-#ifndef strcasecmp
-#define strcasecmp _stricmp
-#endif // strcasecmp
-
using socket_t = SOCKET;
#ifdef CPPHTTPLIB_USE_POLL
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
@@ -172,9 +172,15 @@ using socket_t = SOCKET;
#else // not _WIN32
#include <arpa/inet.h>
-#ifndef _AIX
+#if !defined(_AIX) && !defined(__MVS__)
#include <ifaddrs.h>
#endif
+#ifdef __MVS__
+#include <strings.h>
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#endif
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
@@ -187,6 +193,7 @@ using socket_t = SOCKET;
#endif
#include <csignal>
#include <pthread.h>
+#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -207,6 +214,7 @@ using socket_t = int;
#include <condition_variable>
#include <cstring>
#include <errno.h>
+#include <exception>
#include <fcntl.h>
#include <fstream>
#include <functional>
@@ -223,6 +231,9 @@ using socket_t = int;
#include <string>
#include <sys/stat.h>
#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef _WIN32
@@ -237,7 +248,6 @@ using socket_t = int;
#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>
@@ -259,10 +269,8 @@ using socket_t = int;
#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
-#define SSL_get1_peer_certificate SSL_get_peer_certificate
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
#endif
#endif
@@ -321,7 +329,7 @@ struct scope_exit {
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();
@@ -344,6 +352,81 @@ private:
} // namespace detail
+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::multimap<std::string, std::string, detail::ci>;
using Params = std::multimap<std::string, std::string>;
@@ -373,17 +456,18 @@ public:
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;
}
@@ -465,6 +549,7 @@ struct Request {
MultipartFormDataMap files;
Ranges ranges;
Match matches;
+ std::unordered_map<std::string, std::string> path_params;
// for client
ResponseHandler response_handler;
@@ -476,8 +561,7 @@ struct Request {
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
- template <typename T>
- T 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;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
@@ -509,14 +593,14 @@ struct Response {
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
- template <typename T>
- T 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;
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,
@@ -573,15 +657,16 @@ public:
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--;
@@ -591,13 +676,17 @@ public:
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 {
@@ -647,6 +736,7 @@ private:
std::list<std::function<void()>> jobs_;
bool shutdown_;
+ size_t max_queued_requests_ = 0;
std::condition_variable cond_;
std::mutex mutex_;
@@ -658,6 +748,82 @@ using SocketOptions = std::function<void(socket_t sock)>;
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 {
+public:
+ virtual ~MatcherBase() = default;
+
+ // Match request path and populate its matches and
+ virtual bool match(Request &request) const = 0;
+};
+
+/**
+ * Captures parameters in request path and stores them in Request::path_params
+ *
+ * Capture name is a substring of a pattern from : to /.
+ * The rest of the pattern is matched agains the request path directly
+ * Parameters are captured starting from the next character after
+ * the end of the last matched static pattern fragment until the next /.
+ *
+ * Example pattern:
+ * "/path/fragments/:capture/more/fragments/:second_capture"
+ * Static fragments:
+ * "/path/fragments/", "more/fragments/"
+ *
+ * Given the following request path:
+ * "/path/fragments/:1/more/fragments/:2"
+ * the resulting capture will be
+ * {{"capture", "1"}, {"second_capture", "2"}}
+ */
+class PathParamsMatcher 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
+ static constexpr char separator = '/';
+
+ // Contains static path fragments to match against, excluding the '/' after
+ // path params
+ // Fragments are separated by path params
+ std::vector<std::string> static_fragments_;
+ // Stores the names of the path parameters to be used as keys in the
+ // Request::path_params map
+ std::vector<std::string> param_names_;
+};
+
+/**
+ * Performs std::regex_match on request path
+ * and stores the result in Request::matches
+ *
+ * Note that regex match is performed directly on the whole request.
+ * This means that wildcard patterns may match multiple path segments with /:
+ * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
+ */
+class RegexMatcher final : public MatcherBase {
+public:
+ RegexMatcher(const std::string &pattern) : regex_(pattern) {}
+
+ bool match(Request &request) const override;
+
+private:
+ std::regex regex_;
+};
+
+ssize_t write_headers(Stream &strm, const Headers &headers);
+
+} // namespace detail
+
class Server {
public:
using Handler = std::function<void(const Request &, Response &)>;
@@ -702,6 +868,7 @@ public:
bool remove_mount_point(const std::string &mount_point);
Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
const std::string &mime);
+ Server &set_default_file_mimetype(const std::string &mime);
Server &set_file_request_handler(Handler handler);
Server &set_error_handler(HandlerWithResponse handler);
@@ -718,6 +885,8 @@ public:
Server &set_socket_options(SocketOptions socket_options);
Server &set_default_headers(Headers headers);
+ Server &
+ set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
Server &set_keep_alive_max_count(size_t count);
Server &set_keep_alive_timeout(time_t sec);
@@ -765,9 +934,14 @@ protected:
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
private:
- using Handlers = std::vector<std::pair<std::regex, Handler>>;
+ using Handlers =
+ std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
using HandlersForContentReader =
- std::vector<std::pair<std::regex, HandlerWithContentReader>>;
+ std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
+ HandlerWithContentReader>>;
+
+ static std::unique_ptr<detail::MatcherBase>
+ make_matcher(const std::string &pattern);
socket_t create_server_socket(const std::string &host, int port,
int socket_flags,
@@ -778,16 +952,16 @@ private:
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);
@@ -806,21 +980,23 @@ private:
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};
+
struct MountPointEntry {
std::string mount_point;
std::string base_dir;
Headers headers;
};
std::vector<MountPointEntry> base_dirs_;
-
- std::atomic<bool> is_running_{false};
- std::atomic<bool> done_{false};
std::map<std::string, std::string> file_extension_and_mimetype_map_;
+ std::string default_file_mimetype_ = "application/octet-stream";
Handler file_request_handler_;
+
Handlers get_handlers_;
Handlers post_handlers_;
HandlersForContentReader post_handlers_for_content_reader_;
@@ -831,18 +1007,22 @@ private:
Handlers delete_handlers_;
HandlersForContentReader delete_handlers_for_content_reader_;
Handlers options_handlers_;
+
HandlerWithResponse error_handler_;
ExceptionHandler exception_handler_;
HandlerWithResponse pre_routing_handler_;
Handler post_routing_handler_;
- Logger logger_;
Expect100ContinueHandler expect_100_continue_handler_;
+ Logger logger_;
+
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
SocketOptions socket_options_ = default_socket_options;
Headers default_headers_;
+ std::function<ssize_t(Stream &, Headers &)> header_writer_ =
+ detail::write_headers;
};
enum class Error {
@@ -860,17 +1040,19 @@ enum class Error {
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
+ ProxyConnection,
// For internal use only
SSLPeerCouldBeClosed_,
};
-std::string to_string(const Error error);
+std::string to_string(Error error);
std::ostream &operator<<(std::ostream &os, const Error &obj);
class Result {
public:
+ Result() = default;
Result(std::unique_ptr<Response> &&res, Error err,
Headers &&request_headers = Headers{})
: res_(std::move(res)), err_(err),
@@ -893,13 +1075,13 @@ public:
bool has_request_header(const std::string &key) const;
std::string get_request_header_value(const std::string &key,
size_t id = 0) const;
- template <typename T>
- T get_request_header_value(const std::string &key, size_t id = 0) const;
+ uint64_t get_request_header_value_u64(const std::string &key,
+ size_t id = 0) const;
size_t get_request_header_value_count(const std::string &key) const;
private:
std::unique_ptr<Response> res_;
- Error err_;
+ Error err_ = Error::Unknown;
Headers request_headers_;
};
@@ -1059,16 +1241,21 @@ public:
bool send(Request &req, Response &res, Error &error);
Result send(const Request &req);
- size_t is_socket_open() const;
+ void stop();
- socket_t socket() const;
+ std::string host() const;
+ int port() const;
- void stop();
+ size_t is_socket_open() const;
+ socket_t socket() const;
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_default_headers(Headers headers);
+ void
+ set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
+
void set_address_family(int family);
void set_tcp_nodelay(bool on);
void set_socket_options(SocketOptions socket_options);
@@ -1117,6 +1304,7 @@ public:
void set_ca_cert_path(const std::string &ca_cert_file_path,
const std::string &ca_cert_dir_path = std::string());
void set_ca_cert_store(X509_STORE *ca_cert_store);
+ X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -1145,14 +1333,14 @@ protected:
// 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);
@@ -1177,6 +1365,10 @@ protected:
// Default headers
Headers default_headers_;
+ // Header writer
+ std::function<ssize_t(Stream &, Headers &)> header_writer_ =
+ detail::write_headers;
+
// Settings
std::string client_cert_path_;
std::string client_key_path_;
@@ -1239,7 +1431,8 @@ private:
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);
@@ -1258,7 +1451,7 @@ private:
const std::string &content_type);
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;
@@ -1431,16 +1624,21 @@ public:
bool send(Request &req, Response &res, Error &error);
Result send(const Request &req);
- size_t is_socket_open() const;
+ void stop();
- socket_t socket() const;
+ std::string host() const;
+ int port() const;
- void stop();
+ size_t is_socket_open() const;
+ socket_t socket() const;
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_default_headers(Headers headers);
+ void
+ set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
+
void set_address_family(int family);
void set_tcp_nodelay(bool on);
void set_socket_options(SocketOptions socket_options);
@@ -1497,6 +1695,7 @@ public:
const std::string &ca_cert_dir_path = std::string());
void set_ca_cert_store(X509_STORE *ca_cert_store);
+ void load_ca_cert_store(const char *ca_cert, std::size_t size);
long get_openssl_verify_result() const;
@@ -1538,7 +1737,7 @@ private:
std::mutex ctx_mutex_;
};
-class SSLClient : public ClientImpl {
+class SSLClient final : public ClientImpl {
public:
explicit SSLClient(const std::string &host);
@@ -1546,16 +1745,19 @@ public:
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;
bool is_valid() const override;
void set_ca_cert_store(X509_STORE *ca_cert_store);
+ void load_ca_cert_store(const char *ca_cert, std::size_t size);
long get_openssl_verify_result() const;
@@ -1564,7 +1766,7 @@ public:
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;
@@ -1608,15 +1810,9 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}
-template <typename T>
-inline T get_header_value(const Headers & /*headers*/,
- const std::string & /*key*/, size_t /*id*/ = 0,
- uint64_t /*def*/ = 0) {}
-
-template <>
-inline uint64_t get_header_value<uint64_t>(const Headers &headers,
- const std::string &key, size_t id,
- uint64_t def) {
+inline uint64_t get_header_value_u64(const Headers &headers,
+ const std::string &key, size_t id,
+ uint64_t def) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
@@ -1628,14 +1824,14 @@ inline uint64_t get_header_value<uint64_t>(const Headers &headers,
} // namespace detail
-template <typename T>
-inline T Request::get_header_value(const std::string &key, size_t id) const {
- return detail::get_header_value<T>(headers, key, id, 0);
+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);
}
-template <typename T>
-inline T Response::get_header_value(const std::string &key, size_t id) const {
- return detail::get_header_value<T>(headers, key, id, 0);
+inline uint64_t Response::get_header_value_u64(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value_u64(headers, key, id, 0);
}
template <typename... Args>
@@ -1665,21 +1861,106 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
inline void default_socket_options(socket_t sock) {
int yes = 1;
#ifdef _WIN32
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
- sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const char *>(&yes), sizeof(yes));
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
- reinterpret_cast<char *>(&yes), sizeof(yes));
+ reinterpret_cast<const char *>(&yes), sizeof(yes));
#else
#ifdef SO_REUSEPORT
- setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
- sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ reinterpret_cast<const void *>(&yes), sizeof(yes));
#else
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
- sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const void *>(&yes), sizeof(yes));
#endif
#endif
}
+inline const char *status_message(int status) {
+ switch (status) {
+ 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 StatusCode::InternalServerError_500: return "Internal Server Error";
+ }
+}
+
+inline std::string get_bearer_token_auth(const Request &req) {
+ if (req.has_header("Authorization")) {
+ static std::string BearerHeaderPrefix = "Bearer ";
+ return req.get_header_value("Authorization")
+ .substr(BearerHeaderPrefix.length());
+ }
+ return "";
+}
+
template <class Rep, class Period>
inline Server &
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
@@ -1720,6 +2001,7 @@ inline std::string to_string(const Error error) {
return "Unsupported HTTP multipart boundary characters";
case Error::Compression: return "Compression failed";
case Error::ConnectionTimeout: return "Connection timed out";
+ case Error::ProxyConnection: return "Proxy connection failed";
case Error::Unknown: return "Unknown";
default: break;
}
@@ -1733,10 +2015,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
return os;
}
-template <typename T>
-inline T Result::get_request_header_value(const std::string &key,
- size_t id) const {
- return detail::get_header_value<T>(request_headers_, key, id, 0);
+inline uint64_t Result::get_request_header_value_u64(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value_u64(request_headers_, key, id, 0);
}
template <class Rep, class Period>
@@ -1790,7 +2071,7 @@ void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
std::string append_query_params(const std::string &path, const Params &params);
-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,
@@ -1810,6 +2091,9 @@ std::string trim_copy(const std::string &s);
void split(const char *b, const char *e, char d,
std::function<void(const char *, const char *)> fn);
+void split(const char *b, const char *e, char d, size_t m,
+ std::function<void(const char *, const char *)> fn);
+
bool process_client_socket(socket_t sock, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec,
@@ -1844,7 +2128,7 @@ enum class EncodingType { None = 0, Gzip, Brotli };
EncodingType encoding_type(const Request &req, const Response &res);
-class BufferStream : public Stream {
+class BufferStream final : public Stream {
public:
BufferStream() = default;
~BufferStream() override = default;
@@ -1884,19 +2168,19 @@ public:
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;
@@ -1906,10 +2190,10 @@ private:
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;
@@ -1923,7 +2207,7 @@ private:
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
-class brotli_compressor : public compressor {
+class brotli_compressor final : public compressor {
public:
brotli_compressor();
~brotli_compressor();
@@ -1935,7 +2219,7 @@ private:
BrotliEncoderState *state_ = nullptr;
};
-class brotli_decompressor : public decompressor {
+class brotli_decompressor final : public decompressor {
public:
brotli_decompressor();
~brotli_decompressor();
@@ -1972,6 +2256,29 @@ private:
std::string glowable_buffer_;
};
+class mmap {
+public:
+ mmap(const char *path);
+ ~mmap();
+
+ bool open(const char *path);
+ void close();
+
+ bool is_open() const;
+ size_t size() const;
+ const char *data() const;
+
+private:
+#if defined(_WIN32)
+ HANDLE hFile_;
+ HANDLE hMapping_;
+#else
+ int fd_;
+#endif
+ size_t size_;
+ void *addr_;
+};
+
} // namespace detail
// ----------------------------------------------------------------------------
@@ -2003,7 +2310,7 @@ inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
val = 0;
for (; cnt; i++, cnt--) {
if (!s[i]) { return false; }
- int v = 0;
+ auto v = 0;
if (is_hex(s[i], v)) {
val = val * 16 + v;
} else {
@@ -2014,7 +2321,7 @@ inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
}
inline std::string from_i_to_hex(size_t n) {
- const char *charset = "0123456789abcdef";
+ static const auto charset = "0123456789abcdef";
std::string ret;
do {
ret = charset[n & 15] + ret;
@@ -2025,7 +2332,7 @@ inline std::string from_i_to_hex(size_t n) {
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));
@@ -2064,8 +2371,8 @@ inline std::string base64_encode(const std::string &in) {
std::string out;
out.reserve(in.size());
- int val = 0;
- int valb = -6;
+ auto val = 0;
+ auto valb = -6;
for (auto c : in) {
val = (val << 8) + static_cast<uint8_t>(c);
@@ -2112,6 +2419,11 @@ inline bool is_valid_path(const std::string &path) {
// Read component
auto beg = i;
while (i < path.size() && path[i] != '/') {
+ if (path[i] == '\0') {
+ return false;
+ } else if (path[i] == '\\') {
+ return false;
+ }
i++;
}
@@ -2196,7 +2508,7 @@ inline std::string decode_url(const std::string &s,
for (size_t i = 0; i < s.size(); i++) {
if (s[i] == '%' && i + 1 < s.size()) {
if (s[i + 1] == 'u') {
- int val = 0;
+ auto val = 0;
if (from_hex_to_i(s, i + 2, 4, val)) {
// 4 digits Unicode codes
char buff[4];
@@ -2207,7 +2519,7 @@ inline std::string decode_url(const std::string &s,
result += s[i];
}
} else {
- int val = 0;
+ auto val = 0;
if (from_hex_to_i(s, i + 1, 2, val)) {
// 2 digits hex codes
result += static_cast<char>(val);
@@ -2260,16 +2572,30 @@ inline std::string trim_copy(const std::string &s) {
return s.substr(r.first, r.second - r.first);
}
+inline std::string trim_double_quotes_copy(const std::string &s) {
+ if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
+ return s.substr(1, s.size() - 2);
+ }
+ return s;
+}
+
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++;
}
@@ -2345,6 +2671,105 @@ inline void stream_line_reader::append(char c) {
}
}
+inline mmap::mmap(const char *path)
+#if defined(_WIN32)
+ : hFile_(NULL), hMapping_(NULL)
+#else
+ : fd_(-1)
+#endif
+ ,
+ size_(0), addr_(nullptr) {
+ open(path);
+}
+
+inline mmap::~mmap() { close(); }
+
+inline bool mmap::open(const char *path) {
+ close();
+
+#if defined(_WIN32)
+ std::wstring wpath;
+ for (size_t i = 0; i < strlen(path); i++) {
+ wpath += path[i];
+ }
+
+ hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
+ OPEN_EXISTING, NULL);
+
+ if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
+
+ LARGE_INTEGER size{};
+ if (!::GetFileSizeEx(hFile_, &size)) { return false; }
+ size_ = static_cast<size_t>(size.QuadPart);
+
+ hMapping_ =
+ ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
+
+ if (hMapping_ == NULL) {
+ close();
+ return false;
+ }
+
+ addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
+#else
+ fd_ = ::open(path, O_RDONLY);
+ if (fd_ == -1) { return false; }
+
+ struct stat sb;
+ if (fstat(fd_, &sb) == -1) {
+ close();
+ return false;
+ }
+ size_ = static_cast<size_t>(sb.st_size);
+
+ addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
+#endif
+
+ if (addr_ == nullptr) {
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+inline bool mmap::is_open() const { return addr_ != nullptr; }
+
+inline size_t mmap::size() const { return size_; }
+
+inline const char *mmap::data() const {
+ return static_cast<const char *>(addr_);
+}
+
+inline void mmap::close() {
+#if defined(_WIN32)
+ if (addr_) {
+ ::UnmapViewOfFile(addr_);
+ addr_ = nullptr;
+ }
+
+ if (hMapping_) {
+ ::CloseHandle(hMapping_);
+ hMapping_ = NULL;
+ }
+
+ if (hFile_ != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(hFile_);
+ hFile_ = INVALID_HANDLE_VALUE;
+ }
+#else
+ if (addr_ != nullptr) {
+ munmap(addr_, size_);
+ addr_ = nullptr;
+ }
+
+ if (fd_ != -1) {
+ ::close(fd_);
+ fd_ = -1;
+ }
+#endif
+ size_ = 0;
+}
inline int close_socket(socket_t sock) {
#ifdef _WIN32
return closesocket(sock);
@@ -2354,7 +2779,7 @@ inline int close_socket(socket_t sock) {
}
template <typename T> inline ssize_t handle_EINTR(T fn) {
- ssize_t res = false;
+ ssize_t res = 0;
while (true) {
res = fn();
if (res < 0 && errno == EINTR) { continue; }
@@ -2399,7 +2824,7 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return 1; }
+ if (sock >= FD_SETSIZE) { return -1; }
#endif
fd_set fds;
@@ -2427,7 +2852,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return 1; }
+ if (sock >= FD_SETSIZE) { return -1; }
#endif
fd_set fds;
@@ -2458,7 +2883,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
if (poll_res == 0) { return Error::ConnectionTimeout; }
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
- int error = 0;
+ auto error = 0;
socklen_t len = sizeof(error);
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char *>(&error), &len);
@@ -2490,7 +2915,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
if (ret == 0) { return Error::ConnectionTimeout; }
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
- int error = 0;
+ auto error = 0;
socklen_t len = sizeof(error);
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char *>(&error), &len);
@@ -2512,7 +2937,7 @@ 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);
@@ -2537,11 +2962,11 @@ private:
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,
@@ -2666,7 +3091,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
#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; }
auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
if (sock != INVALID_SOCKET) {
@@ -2735,17 +3160,27 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
#endif
if (tcp_nodelay) {
- int yes = 1;
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
- sizeof(yes));
+ auto yes = 1;
+#ifdef _WIN32
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<const char *>(&yes), sizeof(yes));
+#else
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<const void *>(&yes), sizeof(yes));
+#endif
}
if (socket_options) { socket_options(sock); }
if (rp->ai_family == AF_INET6) {
- int no = 0;
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
- sizeof(no));
+ auto no = 0;
+#ifdef _WIN32
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ reinterpret_cast<const char *>(&no), sizeof(no));
+#else
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ reinterpret_cast<const void *>(&no), sizeof(no));
+#endif
}
// bind or connect
@@ -2804,7 +3239,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
return ret;
}
-#if !defined _WIN32 && !defined ANDROID && !defined _AIX
+#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
#define USE_IF2IP
#endif
@@ -2860,7 +3295,7 @@ inline socket_t create_client_socket(
#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;
}
@@ -2888,13 +3323,14 @@ inline socket_t create_client_socket(
#ifdef _WIN32
auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
read_timeout_usec / 1000);
- setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
- sizeof(timeout));
+ setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<const char *>(&timeout), sizeof(timeout));
#else
timeval tv;
tv.tv_sec = static_cast<long>(read_timeout_sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
- setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<const void *>(&tv), sizeof(tv));
#endif
}
{
@@ -2902,13 +3338,14 @@ inline socket_t create_client_socket(
#ifdef _WIN32
auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
write_timeout_usec / 1000);
- setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
- sizeof(timeout));
+ setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
+ reinterpret_cast<const char *>(&timeout), sizeof(timeout));
#else
timeval tv;
tv.tv_sec = static_cast<long>(write_timeout_sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
- setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
+ reinterpret_cast<const void *>(&tv), sizeof(tv));
#endif
}
@@ -3008,18 +3445,20 @@ inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
} // namespace udl
-inline const char *
+inline std::string
find_content_type(const std::string &path,
- const std::map<std::string, std::string> &user_data) {
+ const std::map<std::string, std::string> &user_data,
+ const std::string &default_content_type) {
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;
switch (str2tag(ext)) {
- default: return nullptr;
+ default: return default_content_type;
+
case "css"_t: return "text/css";
case "csv"_t: return "text/csv";
case "htm"_t:
@@ -3072,76 +3511,6 @@ find_content_type(const std::string &path,
}
}
-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";
-
- default:
- case 500: return "Internal Server Error";
- }
-}
-
inline bool can_compress_content_type(const std::string &content_type) {
using udl::operator""_t;
@@ -3218,7 +3587,7 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length,
data += strm_.avail_in;
auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
- int ret = Z_OK;
+ auto ret = Z_OK;
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
do {
@@ -3262,7 +3631,7 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
Callback callback) {
assert(is_valid_);
- int ret = Z_OK;
+ auto ret = Z_OK;
do {
constexpr size_t max_avail_in =
@@ -3276,16 +3645,12 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
data += strm_.avail_in;
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
- while (strm_.avail_in > 0) {
+ while (strm_.avail_in > 0 && ret == Z_OK) {
strm_.avail_out = static_cast<uInt>(buff.size());
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
- auto prev_avail_in = strm_.avail_in;
-
ret = inflate(&strm_, Z_NO_FLUSH);
- if (prev_avail_in - strm_.avail_in == 0) { return false; }
-
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
@@ -3298,7 +3663,7 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
}
}
- if (ret != Z_OK && ret != Z_STREAM_END) return false;
+ if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
} while (data_length > 0);
@@ -3367,7 +3732,7 @@ inline bool brotli_decompressor::decompress(const char *data,
return 0;
}
- const uint8_t *next_in = (const uint8_t *)data;
+ auto next_in = reinterpret_cast<const uint8_t *>(data);
size_t avail_in = data_length;
size_t total_out;
@@ -3437,6 +3802,9 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
}
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)
@@ -3526,11 +3894,7 @@ inline bool read_content_without_length(Stream &strm,
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 (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
r += static_cast<uint64_t>(n);
@@ -3566,7 +3930,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
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; }
}
@@ -3576,7 +3940,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
// Trailer
if (!line_reader.getline()) { return false; }
- while (strcmp(line_reader.ptr(), "\r\n")) {
+ while (strcmp(line_reader.ptr(), "\r\n") != 0) {
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
// Exclude line terminator
@@ -3595,8 +3959,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
}
inline bool is_chunked_transfer_encoding(const Headers &headers) {
- return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
- "chunked");
+ return compare_case_ignore(
+ get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
}
template <typename T, typename U>
@@ -3611,14 +3975,14 @@ bool prepare_content_receiver(T &x, int &status,
#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
}
@@ -3634,7 +3998,7 @@ bool prepare_content_receiver(T &x, int &status,
};
return callback(std::move(out));
} else {
- status = 500;
+ status = StatusCode::InternalServerError_500;
return false;
}
}
@@ -3662,7 +4026,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
} else if (!has_header(x.headers, "Content-Length")) {
ret = read_content_without_length(strm, out);
} else {
- auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+ auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0);
if (len > payload_max_length) {
exceed_payload_max_length = true;
skip_content_with_length(strm, len);
@@ -3672,7 +4036,10 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
}
}
- 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
@@ -3720,6 +4087,8 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
+
while (offset < end_offset && !is_shutting_down()) {
if (!strm.is_writable()) {
error = Error::Write;
@@ -3764,6 +4133,8 @@ write_content_without_length(Stream &strm,
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
+
data_sink.done = [&](void) { data_available = false; };
while (data_available && !is_shutting_down()) {
@@ -3814,6 +4185,8 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
return ok;
};
+ data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
+
auto done_with_trailer = [&](const Headers *trailer) {
if (!ok) { return; }
@@ -3898,7 +4271,8 @@ inline bool redirect(T &cli, Request &req, Response &res,
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();
@@ -3910,7 +4284,8 @@ inline bool redirect(T &cli, Request &req, Response &res,
if (ret) {
req = new_req;
res = new_res;
- res.location = location;
+
+ if (res.location.empty()) { res.location = location; }
}
return ret;
}
@@ -3957,14 +4332,34 @@ inline bool parse_multipart_boundary(const std::string &content_type,
if (pos == std::string::npos) { return false; }
auto end = content_type.find(';', pos);
auto beg = pos + strlen(boundary_keyword);
- boundary = content_type.substr(beg, end - beg);
- if (boundary.length() >= 2 && boundary.front() == '"' &&
- boundary.back() == '"') {
- boundary = boundary.substr(1, boundary.size() - 2);
- }
+ boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
return !boundary.empty();
}
+inline void parse_disposition_params(const std::string &s, Params &params) {
+ std::set<std::string> cache;
+ split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
+ std::string kv(b, e);
+ if (cache.find(kv) != cache.end()) { return; }
+ cache.insert(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);
+ }
+ });
+
+ if (!key.empty()) {
+ params.emplace(trim_double_quotes_copy((key)),
+ trim_double_quotes_copy((val)));
+ }
+ });
+}
+
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
#else
@@ -3975,9 +4370,9 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
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));
- bool all_valid_ranges = true;
+ auto all_valid_ranges = true;
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
- if (!all_valid_ranges) return;
+ 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)) {
@@ -4022,11 +4417,6 @@ public:
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
const MultipartContentHeader &header_callback) {
- // TODO: support 'filename*'
- static const std::regex re_content_disposition(
- R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
- std::regex_constants::icase);
-
buf_append(buf, n);
while (buf_size() > 0) {
@@ -4059,18 +4449,54 @@ public:
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(),
+ [&](std::string &&, std::string &&) {})) {
+ is_valid_ = false;
+ return false;
+ }
+
+ static const std::string header_content_type = "Content-Type:";
+
+ if (start_with_case_ignore(header, header_content_type)) {
+ file_.content_type =
+ trim_copy(header.substr(header_content_type.size()));
} else {
+ static const std::regex re_content_disposition(
+ R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
+ std::regex_constants::icase);
+
std::smatch m;
if (std::regex_match(header, m, re_content_disposition)) {
- file_.name = m[1];
- file_.filename = m[2];
- } else {
- is_valid_ = false;
- return false;
+ Params params;
+ parse_disposition_params(m[1], params);
+
+ auto it = params.find("name");
+ if (it != params.end()) {
+ file_.name = it->second;
+ } else {
+ is_valid_ = false;
+ return false;
+ }
+
+ it = params.find("filename");
+ if (it != params.end()) { file_.filename = it->second; }
+
+ it = params.find("filename*");
+ if (it != params.end()) {
+ // Only allow UTF-8 enconnding...
+ static const std::regex re_rfc5987_encoding(
+ R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
+
+ std::smatch m2;
+ if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
+ file_.filename = decode_url(m2[1], false); // override...
+ } else {
+ is_valid_ = false;
+ return false;
+ }
+ }
}
}
buf_erase(pos + crlf_.size());
@@ -4108,9 +4534,9 @@ public:
buf_erase(crlf_.size());
state_ = 1;
} else {
- if (dash_crlf_.size() > buf_size()) { return true; }
- if (buf_start_with(dash_crlf_)) {
- buf_erase(dash_crlf_.size());
+ if (dash_.size() > buf_size()) { return true; }
+ if (buf_start_with(dash_)) {
+ buf_erase(dash_.size());
is_valid_ = true;
buf_erase(buf_size()); // Remove epilogue
} else {
@@ -4143,7 +4569,6 @@ private:
const std::string dash_ = "--";
const std::string crlf_ = "\r\n";
- const std::string dash_crlf_ = "--\r\n";
std::string boundary_;
std::string dash_boundary_crlf_;
std::string crlf_dash_boundary_;
@@ -4230,28 +4655,32 @@ inline std::string to_lower(const char *beg, const char *end) {
return out;
}
-inline std::string make_multipart_data_boundary() {
+inline std::string random_string(size_t length) {
static 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;
+ static 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);
+ static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(),
+ seed_gen()};
- std::string result = "--cpp-httplib-multipart-data-";
+ static std::mt19937 engine(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++) {
@@ -4304,48 +4733,97 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
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 contant_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;
- auto slen = static_cast<ssize_t>(content_length);
+ // 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
- if (r.first == -1) {
- r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
- r.second = slen - 1;
+ // 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 = contant_len;
+ }
+
+ if (first_pos == -1) {
+ first_pos = contant_len - last_pos;
+ last_pos = contant_len - 1;
+ }
+
+ if (last_pos == -1) { last_pos = contant_len - 1; }
+
+ // Range must be within content length
+ if (!(0 <= first_pos && first_pos <= last_pos &&
+ last_pos <= contant_len - 1)) {
+ return true;
+ }
+
+ // 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) {
+ (void)(content_length); // patch to get rid of "unused parameter" on release build
+ 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));
+
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
}
-inline std::string make_content_range_header_field(size_t offset, size_t length,
- 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 ";
- field += std::to_string(offset);
+ field += std::to_string(st);
field += "-";
- field += std::to_string(offset + length - 1);
+ 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);
@@ -4356,50 +4834,51 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
ctoken("\r\n");
}
- auto offsets = get_range_offset_and_length(req, res.body.size(), i);
- auto offset = offsets.first;
- auto length = offsets.second;
+ auto offset_and_length =
+ get_range_offset_and_length(req.ranges[i], content_length);
ctoken("Content-Range: ");
- stoken(make_content_range_header_field(offset, length, res.body.size()));
+ stoken(make_content_range_header_field(offset_and_length, content_length));
ctoken("\r\n");
ctoken("\r\n");
- if (!content(offset, length)) { return false; }
+
+ if (!content(offset_and_length.first, offset_and_length.second)) {
+ return false;
+ }
ctoken("\r\n");
}
ctoken("--");
stoken(boundary);
- ctoken("--\r\n");
+ ctoken("--");
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) {
@@ -4411,13 +4890,13 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
}
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) {
@@ -4426,18 +4905,6 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
});
}
-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") {
@@ -4471,7 +4938,7 @@ inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
std::stringstream ss;
for (auto i = 0u; i < hash_length; ++i) {
ss << std::hex << std::setw(2) << std::setfill('0')
- << (unsigned int)hash[i];
+ << static_cast<unsigned int>(hash[i]);
}
return ss.str();
@@ -4564,7 +5031,7 @@ inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
auto result = false;
- for (int i = 0; i < CFArrayGetCount(certs); ++i) {
+ for (auto i = 0; i < CFArrayGetCount(certs); ++i) {
const auto cert = reinterpret_cast<const __SecCertificate *>(
CFArrayGetValueAtIndex(certs, i));
@@ -4707,7 +5174,7 @@ inline bool parse_www_authenticate(const Response &res,
s = s.substr(pos + 1);
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
for (auto i = beg; i != std::sregex_iterator(); ++i) {
- auto m = *i;
+ const auto &m = *i;
auto key = s.substr(static_cast<size_t>(m.position(1)),
static_cast<size_t>(m.length(1)));
auto val = m.length(2) > 0
@@ -4724,20 +5191,6 @@ inline bool parse_www_authenticate(const Response &res,
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(
@@ -4782,7 +5235,7 @@ inline void hosted_at(const std::string &hostname,
const auto &addr =
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
std::string ip;
- int dummy = -1;
+ auto dummy = -1;
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
dummy)) {
addrs.push_back(ip);
@@ -4802,10 +5255,11 @@ inline std::string append_query_params(const std::string &path,
}
// 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 += '-';
@@ -4924,7 +5378,7 @@ inline void Response::set_redirect(const std::string &url, int stat) {
if (300 <= stat && stat < 400) {
this->status = stat;
} else {
- this->status = 302;
+ this->status = StatusCode::Found_302;
}
}
}
@@ -4943,13 +5397,22 @@ inline void Response::set_content(const std::string &s,
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;
}
@@ -4959,7 +5422,7 @@ inline void Response::set_content_provider(
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;
}
@@ -4969,7 +5432,7 @@ inline void Response::set_chunked_content_provider(
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;
}
@@ -5010,7 +5473,7 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec), 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;
@@ -5120,6 +5583,99 @@ inline socket_t BufferStream::socket() const { return 0; }
inline const std::string &BufferStream::get_buffer() const { return buffer; }
+inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
+ // One past the last ending position of a path param substring
+ std::size_t last_param_end = 0;
+
+#ifndef CPPHTTPLIB_NO_EXCEPTIONS
+ // Needed to ensure that parameter names are unique during matcher
+ // construction
+ // If exceptions are disabled, only last duplicate path
+ // parameter will be set
+ std::unordered_set<std::string> param_name_set;
+#endif
+
+ while (true) {
+ const auto marker_pos = pattern.find(marker, last_param_end);
+ if (marker_pos == std::string::npos) { break; }
+
+ static_fragments_.push_back(
+ pattern.substr(last_param_end, marker_pos - last_param_end));
+
+ const auto param_name_start = marker_pos + 1;
+
+ auto sep_pos = pattern.find(separator, param_name_start);
+ if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
+
+ auto param_name =
+ pattern.substr(param_name_start, sep_pos - param_name_start);
+
+#ifndef CPPHTTPLIB_NO_EXCEPTIONS
+ if (param_name_set.find(param_name) != param_name_set.cend()) {
+ std::string msg = "Encountered path parameter '" + param_name +
+ "' multiple times in route pattern '" + pattern + "'.";
+ throw std::invalid_argument(msg);
+ }
+#endif
+
+ param_names_.push_back(std::move(param_name));
+
+ last_param_end = sep_pos + 1;
+ }
+
+ if (last_param_end < pattern.length()) {
+ static_fragments_.push_back(pattern.substr(last_param_end));
+ }
+}
+
+inline bool PathParamsMatcher::match(Request &request) const {
+ request.matches = std::smatch();
+ request.path_params.clear();
+ request.path_params.reserve(param_names_.size());
+
+ // One past the position at which the path matched the pattern last time
+ std::size_t starting_pos = 0;
+ for (size_t i = 0; i < static_fragments_.size(); ++i) {
+ const auto &fragment = static_fragments_[i];
+
+ if (starting_pos + fragment.length() > request.path.length()) {
+ return false;
+ }
+
+ // Avoid unnecessary allocation by using strncmp instead of substr +
+ // comparison
+ if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
+ fragment.length()) != 0) {
+ return false;
+ }
+
+ starting_pos += fragment.length();
+
+ // Should only happen when we have a static fragment after a param
+ // Example: '/users/:id/subscriptions'
+ // The 'subscriptions' fragment here does not have a corresponding param
+ if (i >= param_names_.size()) { continue; }
+
+ auto sep_pos = request.path.find(separator, starting_pos);
+ if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
+
+ const auto &param_name = param_names_[i];
+
+ request.path_params.emplace(
+ param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
+
+ // Mark everythin up to '/' as matched
+ starting_pos = sep_pos + 1;
+ }
+ // Returns false if the path is longer than the pattern
+ return starting_pos >= request.path.length();
+}
+
+inline bool RegexMatcher::match(Request &request) const {
+ request.path_params.clear();
+ return std::regex_match(request.path, request.matches, regex_);
+}
+
} // namespace detail
// HTTP server implementation
@@ -5131,69 +5687,72 @@ inline Server::Server()
#endif
}
-inline Server::~Server() {}
+inline Server::~Server() = default;
+
+inline std::unique_ptr<detail::MatcherBase>
+Server::make_matcher(const std::string &pattern) {
+ if (pattern.find("/:") != std::string::npos) {
+ return detail::make_unique<detail::PathParamsMatcher>(pattern);
+ } else {
+ return detail::make_unique<detail::RegexMatcher>(pattern);
+ }
+}
inline Server &Server::Get(const std::string &pattern, Handler handler) {
- get_handlers_.push_back(
- std::make_pair(std::regex(pattern), std::move(handler)));
+ 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(std::regex(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(std::regex(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(std::regex(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(std::regex(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(std::regex(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(std::regex(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(std::regex(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(std::regex(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(std::regex(pattern), std::move(handler)));
+ options_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
return *this;
}
@@ -5231,6 +5790,11 @@ Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
return *this;
}
+inline Server &Server::set_default_file_mimetype(const std::string &mime) {
+ default_file_mimetype_ = mime;
+ return *this;
+}
+
inline Server &Server::set_file_request_handler(Handler handler) {
file_request_handler_ = std::move(handler);
return *this;
@@ -5272,7 +5836,6 @@ inline Server &Server::set_logger(Logger logger) {
inline Server &
Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
expect_100_continue_handler_ = std::move(handler);
-
return *this;
}
@@ -5296,6 +5859,12 @@ inline Server &Server::set_default_headers(Headers headers) {
return *this;
}
+inline Server &Server::set_header_writer(
+ std::function<ssize_t(Stream &, Headers &)> const &writer) {
+ header_writer_ = writer;
+ return *this;
+}
+
inline Server &Server::set_keep_alive_max_count(size_t count) {
keep_alive_max_count_ = count;
return *this;
@@ -5331,8 +5900,7 @@ inline Server &Server::set_payload_max_length(size_t length) {
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;
+ return bind_internal(host, port, socket_flags) >= 0;
}
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
return bind_internal(host, 0, socket_flags);
@@ -5366,7 +5934,7 @@ inline void Server::stop() {
}
}
-inline bool Server::parse_request_line(const char *s, Request &req) {
+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;
@@ -5407,7 +5975,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
size_t count = 0;
detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
- [&](const char *b, const char *e) {
+ 2, [&](const char *b, const char *e) {
switch (count) {
case 0:
req.path = detail::decode_url(std::string(b, e), false);
@@ -5430,7 +5998,10 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
}
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);
}
@@ -5486,11 +6057,11 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
detail::BufferStream bstrm;
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
- detail::status_message(res.status))) {
+ status_message(res.status))) {
return false;
}
- if (!detail::write_headers(bstrm, res.headers)) { return false; }
+ if (!header_writer_(bstrm, res.headers)) { return false; }
// Flush buffer
auto &data = bstrm.get_buffer();
@@ -5508,7 +6079,6 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
res.content_provider_success_ = true;
} else {
- res.content_provider_success_ = false;
ret = false;
}
}
@@ -5533,15 +6103,16 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
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_) {
@@ -5598,7 +6169,7 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
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);
@@ -5617,10 +6188,11 @@ inline bool Server::read_content_with_content_receiver(
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;
@@ -5628,7 +6200,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
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;
}
@@ -5664,7 +6236,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
if (req.is_multipart_form_data()) {
if (!multipart_form_data_parser.is_valid()) {
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
return false;
}
}
@@ -5683,17 +6255,26 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
if (path.back() == '/') { path += "index.html"; }
if (detail::is_file(path)) {
- detail::read_file(path, res.body);
- auto type =
- detail::find_content_type(path, file_extension_and_mimetype_map_);
- if (type) { res.set_header("Content-Type", type); }
for (const auto &kv : entry.headers) {
- res.set_header(kv.first.c_str(), kv.second);
+ res.set_header(kv.first, kv.second);
}
- res.status = req.has_header("Range") ? 206 : 200;
+
+ auto mm = std::make_shared<detail::mmap>(path.c_str());
+ if (!mm->is_open()) { return false; }
+
+ res.set_content_provider(
+ mm->size(),
+ detail::find_content_type(path, file_extension_and_mimetype_map_,
+ default_file_mimetype_),
+ [mm](size_t offset, size_t length, DataSink &sink) -> bool {
+ sink.write(mm->data() + offset, length);
+ return true;
+ });
+
if (!head && file_request_handler_) {
file_request_handler_(req, res);
}
+
return true;
}
}
@@ -5789,13 +6370,14 @@ inline bool Server::listen_internal() {
#ifdef _WIN32
auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
read_timeout_usec_ / 1000);
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
- sizeof(timeout));
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<const char *>(&timeout), sizeof(timeout));
#else
timeval tv;
tv.tv_sec = static_cast<long>(read_timeout_sec_);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<const void *>(&tv), sizeof(tv));
#endif
}
{
@@ -5803,17 +6385,22 @@ inline bool Server::listen_internal() {
#ifdef _WIN32
auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
write_timeout_usec_ / 1000);
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
- sizeof(timeout));
+ setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
+ reinterpret_cast<const char *>(&timeout), sizeof(timeout));
#else
timeval tv;
tv.tv_sec = static_cast<long>(write_timeout_sec_);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
+ reinterpret_cast<const void *>(&tv), sizeof(tv));
#endif
}
- task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
+ if (!task_queue->enqueue(
+ [this, sock]() { process_and_close_socket(sock); })) {
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ }
}
task_queue->shutdown();
@@ -5829,7 +6416,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
}
// File handler
- bool is_head_request = req.method == "HEAD";
+ auto is_head_request = req.method == "HEAD";
if ((req.method == "GET" || is_head_request) &&
handle_file_request(req, res, is_head_request)) {
return true;
@@ -5895,17 +6482,17 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
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 &pattern = x.first;
+ const auto &matcher = x.first;
const auto &handler = x.second;
- if (std::regex_match(req.path, req.matches, pattern)) {
+ if (matcher->match(req)) {
handler(req, res);
return true;
}
@@ -5915,18 +6502,18 @@ inline bool Server::dispatch_request(Request &req, Response &res,
inline void Server::apply_ranges(const Request &req, Response &res,
std::string &content_type,
- std::string &boundary) {
+ std::string &boundary) const {
if (req.ranges.size() > 1) {
- boundary = detail::make_multipart_data_boundary();
-
auto it = res.headers.find("Content-Type");
if (it != res.headers.end()) {
content_type = it->second;
res.headers.erase(it);
}
- res.headers.emplace("Content-Type",
- "multipart/byteranges; boundary=" + boundary);
+ boundary = detail::make_multipart_data_boundary();
+
+ res.set_header("Content-Type",
+ "multipart/byteranges; boundary=" + boundary);
}
auto type = detail::encoding_type(req, res);
@@ -5937,16 +6524,17 @@ inline void Server::apply_ranges(const Request &req, Response &res,
if (req.ranges.empty()) {
length = res.content_length_;
} else if (req.ranges.size() == 1) {
- auto offsets =
- detail::get_range_offset_and_length(req, res.content_length_, 0);
- auto offset = offsets.first;
- 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(
- offset, length, 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 {
@@ -5965,28 +6553,22 @@ inline void Server::apply_ranges(const Request &req, Response &res,
if (req.ranges.empty()) {
;
} else if (req.ranges.size() == 1) {
- auto offsets =
- detail::get_range_offset_and_length(req, res.body.size(), 0);
- auto offset = offsets.first;
- auto length = offsets.second;
+ 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(
- offset, length, res.body.size());
+ offset_and_length, res.body.size());
res.set_header("Content-Range", content_range);
- 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) {
@@ -6025,12 +6607,12 @@ inline void Server::apply_ranges(const Request &req, Response &res,
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 &pattern = x.first;
+ const auto &matcher = x.first;
const auto &handler = x.second;
- if (std::regex_match(req.path, req.matches, pattern)) {
+ if (matcher->match(req)) {
handler(req, res, content_reader);
return true;
}
@@ -6050,15 +6632,10 @@ Server::process_request(Stream &strm, bool close_connection,
if (!line_reader.getline()) { return false; }
Request req;
- Response res;
+ Response res;
res.version = "HTTP/1.1";
-
- for (const auto &header : default_headers_) {
- if (res.headers.find(header.first) == res.headers.end()) {
- res.headers.insert(header);
- }
- }
+ res.headers = default_headers_;
#ifdef _WIN32
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
@@ -6068,7 +6645,7 @@ Server::process_request(Stream &strm, bool close_connection,
if (strm.socket() >= FD_SETSIZE) {
Headers dummy;
detail::read_headers(strm, dummy);
- res.status = 500;
+ res.status = StatusCode::InternalServerError_500;
return write_response(strm, close_connection, req, res);
}
#endif
@@ -6078,14 +6655,14 @@ Server::process_request(Stream &strm, bool close_connection,
if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
Headers dummy;
detail::read_headers(strm, dummy);
- res.status = 414;
+ 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)) {
- res.status = 400;
+ res.status = StatusCode::BadRequest_400;
return write_response(strm, close_connection, req, res);
}
@@ -6109,7 +6686,7 @@ Server::process_request(Stream &strm, bool close_connection,
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);
}
}
@@ -6117,22 +6694,22 @@ Server::process_request(Stream &strm, bool close_connection,
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:
+ case StatusCode::Continue_100:
+ case StatusCode::ExpectationFailed_417:
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
- detail::status_message(status));
+ status_message(status));
break;
default: return write_response(strm, close_connection, req, res);
}
}
- // Rounting
- bool routed = false;
+ // Routing
+ auto routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
routed = routing(req, res, strm);
#else
@@ -6144,7 +6721,7 @@ Server::process_request(Stream &strm, bool close_connection,
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++) {
@@ -6162,17 +6739,29 @@ Server::process_request(Stream &strm, bool close_connection,
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;
+ }
+
+ 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);
}
}
@@ -6272,7 +6861,7 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
// 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_,
@@ -6297,7 +6886,7 @@ inline void ClientImpl::shutdown_ssl(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);
}
@@ -6322,7 +6911,7 @@ inline void ClientImpl::close_socket(Socket &socket) {
}
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());
@@ -6330,9 +6919,9 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
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");
-#else
const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
+#else
+ const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
#endif
std::cmatch m;
@@ -6344,7 +6933,7 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
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
@@ -6492,15 +7081,31 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
if (!ret) { return false; }
+ if (res.get_header_value("Connection") == "close" ||
+ (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
+ // TODO this requires a not-entirely-obvious chain of calls to be correct
+ // for this to be safe.
+
+ // This is safe to call because handle_request is only called by send_
+ // which locks the request mutex during the process. It would be a bug
+ // to call it from a different thread since it's a thread-safety issue
+ // to do these things to the socket if another thread is using the socket.
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+
if (300 < res.status && res.status < 400 && follow_location_) {
req = req_save;
ret = redirect(req, res, 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 =
@@ -6571,7 +7176,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
} 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);
@@ -6579,7 +7184,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &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);
}
@@ -6588,7 +7193,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &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_) {
@@ -6616,32 +7221,32 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
// Prepare additional headers
if (close_connection) {
if (!req.has_header("Connection")) {
- req.headers.emplace("Connection", "close");
+ req.set_header("Connection", "close");
}
}
if (!req.has_header("Host")) {
if (is_ssl()) {
if (port_ == 443) {
- req.headers.emplace("Host", host_);
+ req.set_header("Host", host_);
} else {
- req.headers.emplace("Host", host_and_port_);
+ req.set_header("Host", host_and_port_);
}
} else {
if (port_ == 80) {
- req.headers.emplace("Host", host_);
+ req.set_header("Host", host_);
} else {
- req.headers.emplace("Host", host_and_port_);
+ req.set_header("Host", host_and_port_);
}
}
}
- if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
+ if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
if (!req.has_header("User-Agent")) {
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
- req.headers.emplace("User-Agent", agent);
+ req.set_header("User-Agent", agent);
}
#endif
@@ -6650,23 +7255,23 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
if (!req.is_chunked_content_provider_) {
if (!req.has_header("Content-Length")) {
auto length = std::to_string(req.content_length_);
- req.headers.emplace("Content-Length", length);
+ req.set_header("Content-Length", length);
}
}
} else {
if (req.method == "POST" || req.method == "PUT" ||
req.method == "PATCH") {
- req.headers.emplace("Content-Length", "0");
+ req.set_header("Content-Length", "0");
}
}
} else {
if (!req.has_header("Content-Type")) {
- req.headers.emplace("Content-Type", "text/plain");
+ req.set_header("Content-Type", "text/plain");
}
if (!req.has_header("Content-Length")) {
auto length = std::to_string(req.body.size());
- req.headers.emplace("Content-Length", length);
+ req.set_header("Content-Length", length);
}
}
@@ -6706,7 +7311,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
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());
- detail::write_headers(bstrm, req.headers);
+ header_writer_(bstrm, req.headers);
// Flush buffer
auto &data = bstrm.get_buffer();
@@ -6734,12 +7339,10 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
ContentProvider content_provider,
ContentProviderWithoutLength content_provider_without_length,
const std::string &content_type, Error &error) {
- if (!content_type.empty()) {
- req.headers.emplace("Content-Type", content_type);
- }
+ if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
+ if (compress_) { req.set_header("Content-Encoding", "gzip"); }
#endif
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
@@ -6800,10 +7403,9 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
req.content_provider_ = detail::ContentProviderAdapter(
std::move(content_provider_without_length));
req.is_chunked_content_provider_ = true;
- req.headers.emplace("Transfer-Encoding", "chunked");
+ req.set_header("Transfer-Encoding", "chunked");
} else {
req.body.assign(body, content_length);
- ;
}
}
@@ -6864,7 +7466,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
}
// Body
- if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
+ if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
+ req.method != "CONNECT") {
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
if (req.response_handler && !redirect) {
@@ -6909,24 +7512,6 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
}
}
- if (res.get_header_value("Connection") == "close" ||
- (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
- // TODO this requires a not-entirely-obvious chain of calls to be correct
- // for this to be safe. Maybe a code refactor (such as moving this out to
- // the send function and getting rid of the recursiveness of the mutex)
- // could make this more obvious.
-
- // This is safe to call because process_request is only called by
- // handle_request which is only called by send, which locks the request
- // mutex during the process. It would be a bug to call it from a different
- // thread since it's a thread-safety issue to do these things to the socket
- // if another thread is using the socket.
- std::lock_guard<std::mutex> guard(socket_mutex_);
- shutdown_ssl(socket_, true);
- shutdown_socket(socket_);
- close_socket(socket_);
- }
-
// Log
if (logger_) { logger_(req, res); }
@@ -6935,13 +7520,14 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
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()) {
@@ -6954,12 +7540,13 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
}
DataSink cur_sink;
- bool has_data = true;
+ auto has_data = true;
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();
@@ -7078,14 +7665,15 @@ inline Result ClientImpl::Get(const std::string &path, const Params &params,
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 &params,
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 &params,
@@ -7094,12 +7682,13 @@ inline Result ClientImpl::Get(const std::string &path, const Params &params,
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) {
@@ -7201,7 +7790,7 @@ 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 ClientImpl::Post(const std::string &path, const Headers &headers,
@@ -7214,7 +7803,7 @@ 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
@@ -7423,9 +8012,7 @@ inline Result ClientImpl::Delete(const std::string &path,
req.headers = headers;
req.path = path;
- if (!content_type.empty()) {
- req.headers.emplace("Content-Type", content_type);
- }
+ if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
req.body.assign(body, content_length);
return send_(std::move(req));
@@ -7458,13 +8045,6 @@ inline Result ClientImpl::Options(const std::string &path,
return send_(std::move(req));
}
-inline size_t ClientImpl::is_socket_open() const {
- std::lock_guard<std::mutex> guard(socket_mutex_);
- return socket_.is_open();
-}
-
-inline socket_t ClientImpl::socket() const { return socket_.sock; }
-
inline void ClientImpl::stop() {
std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -7488,6 +8068,17 @@ inline void ClientImpl::stop() {
close_socket(socket_);
}
+inline std::string ClientImpl::host() const { return host_; }
+
+inline int ClientImpl::port() const { return port_; }
+
+inline size_t ClientImpl::is_socket_open() const {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ return socket_.is_open();
+}
+
+inline socket_t ClientImpl::socket() const { return socket_.sock; }
+
inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
connection_timeout_sec_ = sec;
connection_timeout_usec_ = usec;
@@ -7536,6 +8127,11 @@ inline void ClientImpl::set_default_headers(Headers headers) {
default_headers_ = std::move(headers);
}
+inline void ClientImpl::set_header_writer(
+ std::function<ssize_t(Stream &, Headers &)> const &writer) {
+ header_writer_ = writer;
+}
+
inline void ClientImpl::set_address_family(int family) {
address_family_ = family;
}
@@ -7575,9 +8171,7 @@ inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
proxy_digest_auth_username_ = username;
proxy_digest_auth_password_ = password;
}
-#endif
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
const std::string &ca_cert_dir_path) {
ca_cert_file_path_ = ca_cert_file_path;
@@ -7589,9 +8183,34 @@ inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
ca_cert_store_ = ca_cert_store;
}
}
-#endif
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
+ std::size_t size) const {
+ auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
+ if (!mem) { return nullptr; }
+
+ auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
+ if (!inf) {
+ BIO_free_all(mem);
+ return nullptr;
+ }
+
+ auto cts = X509_STORE_new();
+ if (cts) {
+ for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
+ auto itmp = sk_X509_INFO_value(inf, i);
+ if (!itmp) { continue; }
+
+ if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
+ if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
+ }
+ }
+
+ sk_X509_INFO_pop_free(inf, X509_INFO_free);
+ BIO_free_all(mem);
+ return cts;
+}
+
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
server_certificate_verification_ = enabled;
}
@@ -7655,7 +8274,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
U ssl_connect_or_accept,
time_t timeout_sec,
time_t timeout_usec) {
- int res = 0;
+ auto res = 0;
while ((res = ssl_connect_or_accept(ssl)) != 1) {
auto err = SSL_get_error(ssl, res);
switch (err) {
@@ -7718,7 +8337,7 @@ inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
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;
@@ -7736,7 +8355,7 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret < 0) {
auto err = SSL_get_error(ssl_, ret);
- int n = 1000;
+ auto n = 1000;
#ifdef _WIN32
while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
(err == SSL_ERROR_SYSCALL &&
@@ -7769,7 +8388,7 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
auto ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
if (ret < 0) {
auto err = SSL_get_error(ssl_, ret);
- int n = 1000;
+ auto n = 1000;
#ifdef _WIN32
while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
(err == SSL_ERROR_SYSCALL &&
@@ -7822,10 +8441,10 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_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_,
- (char *)private_key_password);
+ SSL_CTX_set_default_passwd_cb_userdata(
+ ctx_,
+ reinterpret_cast<void *>(const_cast<char *>(private_key_password)));
}
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
@@ -7927,16 +8546,23 @@ inline SSLClient::SSLClient(const std::string &host, int port)
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());
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(),
@@ -7948,16 +8574,23 @@ inline SSLClient::SSLClient(const std::string &host, int port,
}
inline SSLClient::SSLClient(const std::string &host, int port,
- X509 *client_cert, EVP_PKEY *client_key)
+ 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_);
@@ -7989,6 +8622,11 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
}
}
+inline void SSLClient::load_ca_cert_store(const char *ca_cert,
+ std::size_t size) {
+ set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size));
+}
+
inline long SSLClient::get_openssl_verify_result() const {
return verify_result_;
}
@@ -8003,14 +8641,14 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
bool &success, Error &error) {
success = true;
- Response res2;
+ Response proxy_res;
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
Request req2;
req2.method = "CONNECT";
req2.path = host_and_port_;
- return process_request(strm, req2, res2, false, error);
+ return process_request(strm, req2, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are no
// requests in flight
@@ -8021,12 +8659,12 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
return false;
}
- if (res2.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;
- if (detail::parse_www_authenticate(res2, auth, true)) {
- Response res3;
+ if (detail::parse_www_authenticate(proxy_res, auth, true)) {
+ proxy_res = Response();
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
@@ -8037,7 +8675,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
req3, auth, 1, detail::random_string(10),
proxy_digest_auth_username_, proxy_digest_auth_password_,
true));
- return process_request(strm, req3, res3, false, error);
+ return process_request(strm, req3, proxy_res, false, error);
})) {
// Thread-safe to close everything because we are assuming there are
// no requests in flight
@@ -8048,17 +8686,28 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
return false;
}
}
- } else {
- res = res2;
- return false;
}
}
+ // 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 != StatusCode::OK_200) {
+ error = Error::ProxyConnection;
+ res = std::move(proxy_res);
+ // Thread-safe to close everything because we are assuming there are
+ // no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ return false;
+ }
+
return true;
}
inline bool SSLClient::load_certs() {
- bool ret = true;
+ auto ret = true;
std::call_once(initialize_cert_, [&]() {
std::lock_guard<std::mutex> guard(ctx_mutex_);
@@ -8134,7 +8783,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return true;
},
[&](SSL *ssl2) {
- SSL_set_tlsext_host_name(ssl2, host_.c_str());
+ // NOTE: Direct call instead of using the OpenSSL macro to suppress
+ // -Wold-style-cast warning
+ // SSL_set_tlsext_host_name(ssl2, host_.c_str());
+ SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
+ static_cast<void *>(const_cast<char *>(host_.c_str())));
return true;
});
@@ -8208,8 +8861,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
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__
@@ -8234,8 +8887,9 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
auto val = sk_GENERAL_NAME_value(alt_names, i);
if (val->type == type) {
- auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
- auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
+ auto name =
+ reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
+ auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
switch (type) {
case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
@@ -8253,7 +8907,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
if (dsn_matched || ip_matched) { ret = true; }
}
- GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
+ GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
+ reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
return ret;
}
@@ -8282,7 +8937,7 @@ inline bool SSLClient::check_host_name(const char *pattern,
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; }
@@ -8361,7 +9016,7 @@ inline Client::Client(const std::string &host, int 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();
@@ -8421,19 +9076,20 @@ inline Result Client::Get(const std::string &path, const Headers &headers,
}
inline Result Client::Get(const std::string &path, const Params &params,
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 &params,
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 &params,
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); }
@@ -8665,12 +9321,16 @@ inline bool Client::send(Request &req, Response &res, Error &error) {
inline Result Client::send(const Request &req) { return cli_->send(req); }
+inline void Client::stop() { cli_->stop(); }
+
+inline std::string Client::host() const { return cli_->host(); }
+
+inline int Client::port() const { return cli_->port(); }
+
inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
inline socket_t Client::socket() const { return cli_->socket(); }
-inline void Client::stop() { cli_->stop(); }
-
inline void
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
cli_->set_hostname_addr_map(std::move(addr_map));
@@ -8680,6 +9340,11 @@ inline void Client::set_default_headers(Headers headers) {
cli_->set_default_headers(std::move(headers));
}
+inline void Client::set_header_writer(
+ std::function<ssize_t(Stream &, Headers &)> const &writer) {
+ cli_->set_header_writer(writer);
+}
+
inline void Client::set_address_family(int family) {
cli_->set_address_family(family);
}
@@ -8754,7 +9419,9 @@ inline void Client::enable_server_certificate_verification(bool enabled) {
}
#endif
-inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
+inline void Client::set_logger(Logger logger) {
+ cli_->set_logger(std::move(logger));
+}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
@@ -8770,6 +9437,10 @@ inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
}
}
+inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
+ set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size));
+}
+
inline long Client::get_openssl_verify_result() const {
if (is_ssl_) {
return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();