summaryrefslogtreecommitdiffstats
path: root/externals/httplib/httplib.h
diff options
context:
space:
mode:
Diffstat (limited to 'externals/httplib/httplib.h')
-rw-r--r--externals/httplib/httplib.h1153
1 files changed, 793 insertions, 360 deletions
diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h
index 002b874b9..ad4ce279e 100644
--- a/externals/httplib/httplib.h
+++ b/externals/httplib/httplib.h
@@ -114,13 +114,14 @@ using socket_t = SOCKET;
#include <arpa/inet.h>
#include <cstring>
+#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef CPPHTTPLIB_USE_POLL
#include <poll.h>
#endif
-#include <pthread.h>
#include <csignal>
+#include <pthread.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
@@ -129,8 +130,9 @@ using socket_t = int;
#define INVALID_SOCKET (-1)
#endif //_WIN32
-#include <cassert>
+#include <array>
#include <atomic>
+#include <cassert>
#include <condition_variable>
#include <errno.h>
#include <fcntl.h>
@@ -145,13 +147,16 @@ using socket_t = int;
#include <string>
#include <sys/stat.h>
#include <thread>
-#include <array>
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#include <openssl/err.h>
+#include <openssl/md5.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
+#include <iomanip>
+#include <sstream>
+
// #if OPENSSL_VERSION_NUMBER < 0x1010100fL
// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
// #endif
@@ -196,27 +201,17 @@ using DataSink = std::function<void(const char *data, size_t data_len)>;
using Done = std::function<void()>;
-using ContentProvider = std::function<void(size_t offset, size_t length, DataSink sink)>;
-
-using ContentProviderWithCloser = std::function<void(size_t offset, size_t length, DataSink sink, Done done)>;
-
-using ContentReceiver = std::function<bool(const char *data, size_t data_length)>;
+using ContentProvider =
+ std::function<void(size_t offset, size_t length, DataSink sink)>;
-using ContentReader = std::function<bool(ContentReceiver receiver)>;
+using ContentProviderWithCloser =
+ std::function<void(size_t offset, size_t length, DataSink sink, Done done)>;
using Progress = std::function<bool(uint64_t current, uint64_t total)>;
struct Response;
using ResponseHandler = std::function<bool(const Response &response)>;
-struct MultipartFile {
- std::string filename;
- std::string content_type;
- size_t offset = 0;
- size_t length = 0;
-};
-using MultipartFiles = std::multimap<std::string, MultipartFile>;
-
struct MultipartFormData {
std::string name;
std::string content;
@@ -224,6 +219,33 @@ struct MultipartFormData {
std::string content_type;
};
using MultipartFormDataItems = std::vector<MultipartFormData>;
+using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
+
+using ContentReceiver =
+ std::function<bool(const char *data, size_t data_length)>;
+
+using MultipartContentHeader =
+ std::function<bool(const MultipartFormData &file)>;
+
+class ContentReader {
+public:
+ using Reader = std::function<bool(ContentReceiver receiver)>;
+ using MultipartReader = std::function<bool(MultipartContentHeader header,
+ ContentReceiver receiver)>;
+
+ ContentReader(Reader reader, MultipartReader muitlpart_reader)
+ : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
+
+ bool operator()(MultipartContentHeader header,
+ ContentReceiver receiver) const {
+ return muitlpart_reader_(header, receiver);
+ }
+
+ bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
+
+ Reader reader_;
+ MultipartReader muitlpart_reader_;
+};
using Range = std::pair<ssize_t, ssize_t>;
using Ranges = std::vector<Range>;
@@ -238,7 +260,7 @@ struct Request {
std::string version;
std::string target;
Params params;
- MultipartFiles files;
+ MultipartFormDataMap files;
Ranges ranges;
Match matches;
@@ -262,8 +284,10 @@ struct Request {
std::string get_param_value(const char *key, size_t id = 0) const;
size_t get_param_value_count(const char *key) const;
+ bool is_multipart_form_data() const;
+
bool has_file(const char *key) const;
- MultipartFile get_file_value(const char *key) const;
+ MultipartFormData get_file_value(const char *key) const;
// private members...
size_t content_length;
@@ -394,7 +418,7 @@ public:
cond_.notify_all();
// Join...
- for (auto& t : threads_) {
+ for (auto &t : threads_) {
t.join();
}
}
@@ -475,20 +499,17 @@ public:
NoThread() {}
virtual ~NoThread() {}
- virtual void enqueue(std::function<void()> fn) override {
- fn();
- }
+ virtual void enqueue(std::function<void()> fn) override { fn(); }
- virtual void shutdown() override {
- }
+ virtual void shutdown() override {}
};
#endif
class Server {
public:
using Handler = std::function<void(const Request &, Response &)>;
- using HandlerWithContentReader = std::function<void(const Request &, Response &,
- const ContentReader &content_reader)>;
+ using HandlerWithContentReader = std::function<void(
+ const Request &, Response &, const ContentReader &content_reader)>;
using Logger = std::function<void(const Request &, const Response &)>;
Server();
@@ -507,7 +528,7 @@ public:
Server &Delete(const char *pattern, Handler handler);
Server &Options(const char *pattern, Handler handler);
- bool set_base_dir(const char *path);
+ bool set_base_dir(const char *dir, const char *mount_point = nullptr);
void set_file_request_handler(Handler handler);
void set_error_handler(Handler handler);
@@ -531,7 +552,7 @@ public:
protected:
bool process_request(Stream &strm, bool last_connection,
bool &connection_close,
- const std::function<void(Request &)>& setup_request);
+ const std::function<void(Request &)> &setup_request);
size_t keep_alive_max_count_;
time_t read_timeout_sec_;
@@ -540,7 +561,8 @@ protected:
private:
using Handlers = std::vector<std::pair<std::regex, Handler>>;
- using HandersForContentReader = std::vector<std::pair<std::regex, HandlerWithContentReader>>;
+ using HandersForContentReader =
+ std::vector<std::pair<std::regex, HandlerWithContentReader>>;
socket_t create_server_socket(const char *host, int port,
int socket_flags) const;
@@ -562,23 +584,28 @@ private:
const std::string &content_type);
bool read_content(Stream &strm, bool last_connection, Request &req,
Response &res);
- bool read_content_with_content_receiver(Stream &strm, bool last_connection,
- Request &req, Response &res,
- ContentReceiver reveiver);
+ bool read_content_with_content_receiver(
+ Stream &strm, bool last_connection, Request &req, Response &res,
+ ContentReceiver receiver, MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver);
+ bool read_content_core(Stream &strm, bool last_connection, Request &req,
+ Response &res, ContentReceiver receiver,
+ MultipartContentHeader mulitpart_header,
+ ContentReceiver multipart_receiver);
virtual bool process_and_close_socket(socket_t sock);
std::atomic<bool> is_running_;
std::atomic<socket_t> svr_sock_;
- std::string base_dir_;
+ std::vector<std::pair<std::string, std::string>> base_dirs_;
Handler file_request_handler_;
Handlers get_handlers_;
Handlers post_handlers_;
- HandersForContentReader post_handlers_for_content_reader;
+ HandersForContentReader post_handlers_for_content_reader_;
Handlers put_handlers_;
- HandersForContentReader put_handlers_for_content_reader;
+ HandersForContentReader put_handlers_for_content_reader_;
Handlers patch_handlers_;
- HandersForContentReader patch_handlers_for_content_reader;
+ HandersForContentReader patch_handlers_for_content_reader_;
Handlers delete_handlers_;
Handlers options_handlers_;
Handler error_handler_;
@@ -629,78 +656,63 @@ public:
std::shared_ptr<Response> Head(const char *path, const Headers &headers);
std::shared_ptr<Response> Post(const char *path, const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Post(const char *path, size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
- std::shared_ptr<Response> Post(const char *path, const Params &params,
- bool compress = false);
+ std::shared_ptr<Response> Post(const char *path, const Params &params);
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const Params &params, bool compress = false);
+ const Params &params);
std::shared_ptr<Response> Post(const char *path,
- const MultipartFormDataItems &items,
- bool compress = false);
+ const MultipartFormDataItems &items);
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items,
- bool compress = false);
+ const MultipartFormDataItems &items);
std::shared_ptr<Response> Put(const char *path, const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Put(const char *path, const Headers &headers,
const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Put(const char *path, size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Put(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Patch(const char *path, const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
const std::string &body,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Patch(const char *path, size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress = false);
+ const char *content_type);
std::shared_ptr<Response> Delete(const char *path);
@@ -723,9 +735,16 @@ public:
std::vector<Response> &responses);
void set_keep_alive_max_count(size_t count);
+
void set_read_timeout(time_t sec, time_t usec);
- void follow_location(bool on);
+ void set_auth(const char *username, const char *password);
+
+ void set_follow_location(bool on);
+
+ void set_compress(bool on);
+
+ void set_interface(const char *intf);
protected:
bool process_request(Stream &strm, const Request &req, Response &res,
@@ -738,20 +757,22 @@ protected:
size_t keep_alive_max_count_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
- size_t follow_location_;
+ bool follow_location_;
+ std::string username_;
+ std::string password_;
+ bool compress_;
+ std::string interface_;
private:
socket_t create_client_socket() const;
bool read_response_line(Stream &strm, Response &res);
- void write_request(Stream &strm, const Request &req, bool last_connection);
+ bool write_request(Stream &strm, const Request &req, bool last_connection);
bool redirect(const Request &req, Response &res);
- std::shared_ptr<Response>
- send_with_content_provider(const char *method, const char *path,
- const Headers &headers, const std::string &body,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type, bool compress);
+ std::shared_ptr<Response> send_with_content_provider(
+ const char *method, const char *path, const Headers &headers,
+ const std::string &body, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
virtual bool process_and_close_socket(
socket_t sock, size_t request_count,
@@ -870,6 +891,8 @@ private:
};
#endif
+// ----------------------------------------------------------------------------
+
/*
* Implementation
*/
@@ -1037,7 +1060,7 @@ inline void read_file(const std::string &path, std::string &out) {
inline std::string file_extension(const std::string &path) {
std::smatch m;
- auto re = std::regex("\\.([a-zA-Z0-9]+)$");
+ static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
if (std::regex_search(path, m, re)) { return m[1].str(); }
return std::string();
}
@@ -1081,6 +1104,11 @@ public:
}
}
+ bool end_with_crlf() const {
+ auto end = ptr() + size();
+ return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
+ }
+
bool getline() {
fixed_buffer_used_size_ = 0;
glowable_buffer_.clear();
@@ -1191,7 +1219,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
int error = 0;
socklen_t len = sizeof(error);
- return getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &len) >= 0 &&
+ return getsockopt(sock, SOL_SOCKET, SO_ERROR,
+ reinterpret_cast<char *>(&error), &len) >= 0 &&
!error;
}
return false;
@@ -1323,6 +1352,78 @@ inline bool is_connection_error() {
#endif
}
+inline bool bind_ip_address(socket_t sock, const char *host) {
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ if (getaddrinfo(host, "0", &hints, &result)) { return false; }
+
+ bool ret = false;
+ for (auto rp = result; rp; rp = rp->ai_next) {
+ const auto &ai = *rp;
+ if (!::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+ ret = true;
+ break;
+ }
+ }
+
+ freeaddrinfo(result);
+ return ret;
+}
+
+inline std::string if2ip(const std::string &ifn) {
+#ifndef _WIN32
+ struct ifaddrs *ifap;
+ getifaddrs(&ifap);
+ for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr && ifn == ifa->ifa_name) {
+ if (ifa->ifa_addr->sa_family == AF_INET) {
+ auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
+ char buf[INET_ADDRSTRLEN];
+ if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
+ freeifaddrs(ifap);
+ return std::string(buf, INET_ADDRSTRLEN);
+ }
+ }
+ }
+ }
+ freeifaddrs(ifap);
+#endif
+ return std::string();
+}
+
+inline socket_t create_client_socket(const char *host, int port,
+ time_t timeout_sec,
+ const std::string &intf) {
+ return create_socket(
+ host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
+ if (!intf.empty()) {
+ auto ip = if2ip(intf);
+ if (ip.empty()) { ip = intf; }
+ if (!bind_ip_address(sock, ip.c_str())) { return false; }
+ }
+
+ set_nonblocking(sock, true);
+
+ auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
+ if (ret < 0) {
+ if (is_connection_error() ||
+ !wait_until_socket_is_ready(sock, timeout_sec, 0)) {
+ close_socket(sock);
+ return false;
+ }
+ }
+
+ set_nonblocking(sock, false);
+ return true;
+ });
+}
+
inline std::string get_remote_addr(socket_t sock) {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
@@ -1330,8 +1431,8 @@ inline std::string get_remote_addr(socket_t sock) {
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
std::array<char, NI_MAXHOST> ipstr{};
- if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len, ipstr.data(), ipstr.size(),
- nullptr, 0, NI_NUMERICHOST)) {
+ if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
+ ipstr.data(), ipstr.size(), nullptr, 0, NI_NUMERICHOST)) {
return ipstr.data();
}
}
@@ -1380,6 +1481,7 @@ inline const char *status_message(int status) {
case 303: return "See Other";
case 304: return "Not Modified";
case 400: return "Bad Request";
+ case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 413: return "Payload Too Large";
@@ -1420,7 +1522,7 @@ inline bool compress(std::string &content) {
std::array<char, 16384> buff{};
do {
strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef*>(buff.data());
+ strm.next_out = reinterpret_cast<Bytef *>(buff.data());
ret = deflate(&strm, Z_FINISH);
assert(ret != Z_STREAM_ERROR);
compressed.append(buff.data(), buff.size() - strm.avail_out);
@@ -1462,7 +1564,7 @@ public:
std::array<char, 16384> buff{};
do {
strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef*>(buff.data());
+ strm.next_out = reinterpret_cast<Bytef *>(buff.data());
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
@@ -1472,7 +1574,9 @@ public:
case Z_MEM_ERROR: inflateEnd(&strm); return false;
}
- if (!callback(buff.data(), buff.size() - strm.avail_out)) { return false; }
+ if (!callback(buff.data(), buff.size() - strm.avail_out)) {
+ return false;
+ }
} while (strm.avail_out == 0);
return ret == Z_OK || ret == Z_STREAM_END;
@@ -1506,18 +1610,35 @@ inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
}
inline bool read_headers(Stream &strm, Headers &headers) {
- static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)");
-
const auto bufsiz = 2048;
char buf[bufsiz];
-
stream_line_reader line_reader(strm, buf, bufsiz);
for (;;) {
if (!line_reader.getline()) { return false; }
- if (!strcmp(line_reader.ptr(), "\r\n")) { break; }
+
+ // Check if the line ends with CRLF.
+ if (line_reader.end_with_crlf()) {
+ // Blank line indicates end of headers.
+ if (line_reader.size() == 2) { break; }
+ } else {
+ continue; // Skip invalid line.
+ }
+
+ // Skip trailing spaces and tabs.
+ auto end = line_reader.ptr() + line_reader.size() - 2;
+ while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) {
+ end--;
+ }
+
+ // Horizontal tab and ' ' are considered whitespace and are ignored when on
+ // the left or right side of the header value:
+ // - https://stackoverflow.com/questions/50179659/
+ // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
+ static const std::regex re(R"((.+?):[\t ]*(.+))");
+
std::cmatch m;
- if (std::regex_match(line_reader.ptr(), m, re)) {
+ if (std::regex_match(line_reader.ptr(), end, m, re)) {
auto key = std::string(m[1]);
auto val = std::string(m[2]);
headers.emplace(key, val);
@@ -1844,113 +1965,206 @@ inline bool parse_multipart_boundary(const std::string &content_type,
return true;
}
-inline bool parse_multipart_formdata(const std::string &boundary,
- const std::string &body,
- MultipartFiles &files) {
- static std::string dash = "--";
- static std::string crlf = "\r\n";
-
- static std::regex re_content_type("Content-Type: (.*?)$",
- std::regex_constants::icase);
-
- static std::regex re_content_disposition(
- "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?",
- std::regex_constants::icase);
-
- auto dash_boundary = dash + boundary;
-
- auto pos = body.find(dash_boundary);
- if (pos != 0) { return false; }
-
- pos += dash_boundary.size();
+inline bool parse_range_header(const std::string &s, Ranges &ranges) {
+ static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
+ std::smatch m;
+ if (std::regex_match(s, m, re_first_range)) {
+ auto pos = m.position(1);
+ auto len = m.length(1);
+ bool all_valid_ranges = true;
+ detail::split(
+ &s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
+ if (!all_valid_ranges) return;
+ static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
+ std::cmatch m;
+ if (std::regex_match(b, e, m, re_another_range)) {
+ ssize_t first = -1;
+ if (!m.str(1).empty()) {
+ first = static_cast<ssize_t>(std::stoll(m.str(1)));
+ }
- auto next_pos = body.find(crlf, pos);
- if (next_pos == std::string::npos) { return false; }
+ ssize_t last = -1;
+ if (!m.str(2).empty()) {
+ last = static_cast<ssize_t>(std::stoll(m.str(2)));
+ }
- pos = next_pos + crlf.size();
+ if (first != -1 && last != -1 && first > last) {
+ all_valid_ranges = false;
+ return;
+ }
+ ranges.emplace_back(std::make_pair(first, last));
+ }
+ });
+ return all_valid_ranges;
+ }
+ return false;
+}
- while (pos < body.size()) {
- next_pos = body.find(crlf, pos);
- if (next_pos == std::string::npos) { return false; }
+class MultipartFormDataParser {
+public:
+ MultipartFormDataParser() {}
- std::string name;
- MultipartFile file;
+ void set_boundary(const std::string &boundary) { boundary_ = boundary; }
- auto header = body.substr(pos, (next_pos - pos));
+ bool is_valid() const { return is_valid_; }
- while (pos != next_pos) {
- std::smatch m;
- if (std::regex_match(header, m, re_content_type)) {
- file.content_type = m[1];
- } else if (std::regex_match(header, m, re_content_disposition)) {
- name = m[1];
- file.filename = m[2];
+ template <typename T, typename U>
+ bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
+ static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)",
+ std::regex_constants::icase);
+
+ static const std::regex re_content_disposition(
+ "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
+ "\"(.*?)\")?\\s*$",
+ std::regex_constants::icase);
+
+ buf_.append(buf, n); // TODO: performance improvement
+
+ while (!buf_.empty()) {
+ switch (state_) {
+ case 0: { // Initial boundary
+ auto pattern = dash_ + boundary_ + crlf_;
+ if (pattern.size() > buf_.size()) { return true; }
+ auto pos = buf_.find(pattern);
+ if (pos != 0) {
+ is_done_ = true;
+ return false;
+ }
+ buf_.erase(0, pattern.size());
+ off_ += pattern.size();
+ state_ = 1;
+ break;
}
+ case 1: { // New entry
+ clear_file_info();
+ state_ = 2;
+ break;
+ }
+ case 2: { // Headers
+ auto pos = buf_.find(crlf_);
+ while (pos != std::string::npos) {
+ // Empty line
+ if (pos == 0) {
+ if (!header_callback(file_)) {
+ is_valid_ = false;
+ is_done_ = false;
+ return false;
+ }
+ buf_.erase(0, crlf_.size());
+ off_ += crlf_.size();
+ state_ = 3;
+ break;
+ }
- pos = next_pos + crlf.size();
-
- next_pos = body.find(crlf, pos);
- if (next_pos == std::string::npos) { return false; }
-
- header = body.substr(pos, (next_pos - pos));
- }
-
- pos = next_pos + crlf.size();
-
- next_pos = body.find(crlf + dash_boundary, pos);
+ auto header = buf_.substr(0, pos);
+ {
+ std::smatch m;
+ if (std::regex_match(header, m, re_content_type)) {
+ file_.content_type = m[1];
+ } else if (std::regex_match(header, m, re_content_disposition)) {
+ file_.name = m[1];
+ file_.filename = m[2];
+ }
+ }
- if (next_pos == std::string::npos) { return false; }
+ buf_.erase(0, pos + crlf_.size());
+ off_ += pos + crlf_.size();
+ pos = buf_.find(crlf_);
+ }
+ break;
+ }
+ case 3: { // Body
+ {
+ auto pattern = crlf_ + dash_;
+ auto pos = buf_.find(pattern);
+ if (pos == std::string::npos) { pos = buf_.size(); }
+ if (!content_callback(buf_.data(), pos)) {
+ is_valid_ = false;
+ is_done_ = false;
+ return false;
+ }
- file.offset = pos;
- file.length = next_pos - pos;
+ off_ += pos;
+ buf_.erase(0, pos);
+ }
- pos = next_pos + crlf.size() + dash_boundary.size();
+ {
+ auto pattern = crlf_ + dash_ + boundary_;
+ if (pattern.size() > buf_.size()) { return true; }
+
+ auto pos = buf_.find(pattern);
+ if (pos != std::string::npos) {
+ if (!content_callback(buf_.data(), pos)) {
+ is_valid_ = false;
+ is_done_ = false;
+ return false;
+ }
- next_pos = body.find(crlf, pos);
- if (next_pos == std::string::npos) { return false; }
+ off_ += pos + pattern.size();
+ buf_.erase(0, pos + pattern.size());
+ state_ = 4;
+ } else {
+ if (!content_callback(buf_.data(), pattern.size())) {
+ is_valid_ = false;
+ is_done_ = false;
+ return false;
+ }
- files.emplace(name, file);
+ off_ += pattern.size();
+ buf_.erase(0, pattern.size());
+ }
+ }
+ break;
+ }
+ case 4: { // Boundary
+ if (crlf_.size() > buf_.size()) { return true; }
+ if (buf_.find(crlf_) == 0) {
+ buf_.erase(0, crlf_.size());
+ off_ += crlf_.size();
+ state_ = 1;
+ } else {
+ auto pattern = dash_ + crlf_;
+ if (pattern.size() > buf_.size()) { return true; }
+ if (buf_.find(pattern) == 0) {
+ buf_.erase(0, pattern.size());
+ off_ += pattern.size();
+ is_valid_ = true;
+ state_ = 5;
+ } else {
+ is_done_ = true;
+ return true;
+ }
+ }
+ break;
+ }
+ case 5: { // Done
+ is_valid_ = false;
+ return false;
+ }
+ }
+ }
- pos = next_pos + crlf.size();
+ return true;
}
- return true;
-}
-
-inline bool parse_range_header(const std::string &s, Ranges &ranges) {
- try {
- static auto re_first_range =
- std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
- std::smatch m;
- if (std::regex_match(s, m, re_first_range)) {
- auto pos = m.position(1);
- auto len = m.length(1);
- detail::split(
- &s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
- static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
- std::cmatch m;
- if (std::regex_match(b, e, m, re_another_range)) {
- ssize_t first = -1;
- if (!m.str(1).empty()) {
- first = static_cast<ssize_t>(std::stoll(m.str(1)));
- }
+private:
+ void clear_file_info() {
+ file_.name.clear();
+ file_.filename.clear();
+ file_.content_type.clear();
+ }
- ssize_t last = -1;
- if (!m.str(2).empty()) {
- last = static_cast<ssize_t>(std::stoll(m.str(2)));
- }
+ const std::string dash_ = "--";
+ const std::string crlf_ = "\r\n";
+ std::string boundary_;
- if (first != -1 && last != -1 && first > last) {
- throw std::runtime_error("invalid range error");
- }
- ranges.emplace_back(std::make_pair(first, last));
- }
- });
- return true;
- }
- return false;
- } catch (...) { return false; }
-}
+ std::string buf_;
+ size_t state_ = 0;
+ size_t is_valid_ = false;
+ size_t is_done_ = false;
+ size_t off_ = 0;
+ MultipartFormData file_;
+};
inline std::string to_lower(const char *beg, const char *end) {
std::string out;
@@ -2102,6 +2316,52 @@ get_range_offset_and_length(const Request &req, const Response &res,
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") {
+ return true;
+ }
+ // TODO: check if Content-Length is set
+ return false;
+}
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+template <typename CTX, typename Init, typename Update, typename Final>
+inline std::string message_digest(const std::string &s, Init init,
+ Update update, Final final,
+ size_t digest_length) {
+ using namespace std;
+
+ std::vector<unsigned char> md(digest_length, 0);
+ CTX ctx;
+ init(&ctx);
+ update(&ctx, s.data(), s.size());
+ final(md.data(), &ctx);
+
+ stringstream ss;
+ for (auto c : md) {
+ ss << setfill('0') << setw(2) << hex << (unsigned int)c;
+ }
+ return ss.str();
+}
+
+inline std::string MD5(const std::string &s) {
+ using namespace detail;
+ return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final,
+ MD5_DIGEST_LENGTH);
+}
+
+inline std::string SHA_256(const std::string &s) {
+ return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
+ SHA256_DIGEST_LENGTH);
+}
+
+inline std::string SHA_512(const std::string &s) {
+ return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
+ SHA512_DIGEST_LENGTH);
+}
+#endif
+
#ifdef _WIN32
class WSInit {
public:
@@ -2139,6 +2399,96 @@ make_basic_authentication_header(const std::string &username,
return std::make_pair("Authorization", field);
}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline std::pair<std::string, std::string> make_digest_authentication_header(
+ const Request &req, const std::map<std::string, std::string> &auth,
+ size_t cnonce_count, const std::string &cnonce, const std::string &username,
+ const std::string &password) {
+ using namespace std;
+
+ string nc;
+ {
+ stringstream ss;
+ ss << setfill('0') << setw(8) << hex << cnonce_count;
+ nc = ss.str();
+ }
+
+ auto qop = auth.at("qop");
+ if (qop.find("auth-int") != std::string::npos) {
+ qop = "auth-int";
+ } else {
+ qop = "auth";
+ }
+
+ string response;
+ {
+ auto algo = auth.at("algorithm");
+
+ auto H = algo == "SHA-256"
+ ? detail::SHA_256
+ : algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
+
+ auto A1 = username + ":" + auth.at("realm") + ":" + password;
+
+ auto A2 = req.method + ":" + req.path;
+ if (qop == "auth-int") { A2 += ":" + H(req.body); }
+
+ response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
+ ":" + qop + ":" + H(A2));
+ }
+
+ auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
+ "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
+ "\", algorithm=" + auth.at("algorithm") + ", qop=" + qop +
+ ", nc=\"" + nc + "\", cnonce=\"" + cnonce + "\", response=\"" +
+ response + "\"";
+
+ return make_pair("Authorization", field);
+}
+#endif
+
+inline int
+parse_www_authenticate(const httplib::Response &res,
+ std::map<std::string, std::string> &digest_auth) {
+ if (res.has_header("WWW-Authenticate")) {
+ static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
+ auto s = res.get_header_value("WWW-Authenticate");
+ auto pos = s.find(' ');
+ if (pos != std::string::npos) {
+ auto type = s.substr(0, pos);
+ if (type == "Basic") {
+ return 1;
+ } else if (type == "Digest") {
+ 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;
+ auto key = s.substr(m.position(1), m.length(1));
+ auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2))
+ : s.substr(m.position(3), m.length(3));
+ digest_auth[key] = val;
+ }
+ return 2;
+ }
+ }
+ }
+ return 0;
+}
+
+// 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[rand() % max_index];
+ };
+ std::string str(length, 0);
+ std::generate_n(str.begin(), length, randchar);
+ return str;
+}
+
// Request implementation
inline bool Request::has_header(const char *key) const {
return detail::has_header(headers, key);
@@ -2177,14 +2527,19 @@ inline size_t Request::get_param_value_count(const char *key) const {
return std::distance(r.first, r.second);
}
+inline bool Request::is_multipart_form_data() const {
+ const auto &content_type = get_header_value("Content-Type");
+ return !content_type.find("multipart/form-data");
+}
+
inline bool Request::has_file(const char *key) const {
return files.find(key) != files.end();
}
-inline MultipartFile Request::get_file_value(const char *key) const {
+inline MultipartFormData Request::get_file_value(const char *key) const {
auto it = files.find(key);
if (it != files.end()) { return it->second; }
- return MultipartFile();
+ return MultipartFormData();
}
// Response implementation
@@ -2369,7 +2724,7 @@ inline Server &Server::Post(const char *pattern, Handler handler) {
inline Server &Server::Post(const char *pattern,
HandlerWithContentReader handler) {
- post_handlers_for_content_reader.push_back(
+ post_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern), handler));
return *this;
}
@@ -2381,7 +2736,7 @@ inline Server &Server::Put(const char *pattern, Handler handler) {
inline Server &Server::Put(const char *pattern,
HandlerWithContentReader handler) {
- put_handlers_for_content_reader.push_back(
+ put_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern), handler));
return *this;
}
@@ -2393,7 +2748,7 @@ inline Server &Server::Patch(const char *pattern, Handler handler) {
inline Server &Server::Patch(const char *pattern,
HandlerWithContentReader handler) {
- patch_handlers_for_content_reader.push_back(
+ patch_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern), handler));
return *this;
}
@@ -2408,10 +2763,13 @@ inline Server &Server::Options(const char *pattern, Handler handler) {
return *this;
}
-inline bool Server::set_base_dir(const char *path) {
- if (detail::is_dir(path)) {
- base_dir_ = path;
- return true;
+inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
+ if (detail::is_dir(dir)) {
+ std::string mnt = mount_point ? mount_point : "/";
+ if (!mnt.empty() && mnt[0] == '/') {
+ base_dirs_.emplace_back(mnt, dir);
+ return true;
+ }
}
return false;
}
@@ -2465,7 +2823,7 @@ inline void Server::stop() {
}
inline bool Server::parse_request_line(const char *s, Request &req) {
- static std::regex re(
+ const static std::regex re(
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
"(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
@@ -2643,62 +3001,106 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
inline bool Server::read_content(Stream &strm, bool last_connection,
Request &req, Response &res) {
- if (!detail::read_content(strm, req, payload_max_length_, res.status,
- Progress(), [&](const char *buf, size_t n) {
- if (req.body.size() + n > req.body.max_size()) {
- return false;
- }
- req.body.append(buf, n);
- return true;
- })) {
- return write_response(strm, last_connection, req, res);
- }
+ MultipartFormDataMap::iterator cur;
+ auto ret = read_content_core(
+ strm, last_connection, req, res,
+ // Regular
+ [&](const char *buf, size_t n) {
+ if (req.body.size() + n > req.body.max_size()) { return false; }
+ req.body.append(buf, n);
+ return true;
+ },
+ // Multipart
+ [&](const MultipartFormData &file) {
+ cur = req.files.emplace(file.name, file);
+ return true;
+ },
+ [&](const char *buf, size_t n) {
+ auto &content = cur->second.content;
+ if (content.size() + n > content.max_size()) { return false; }
+ content.append(buf, n);
+ return true;
+ });
const auto &content_type = req.get_header_value("Content-Type");
-
if (!content_type.find("application/x-www-form-urlencoded")) {
detail::parse_query_text(req.body, req.params);
- } else if (!content_type.find("multipart/form-data")) {
+ }
+
+ return ret;
+}
+
+inline bool Server::read_content_with_content_receiver(
+ Stream &strm, bool last_connection, Request &req, Response &res,
+ ContentReceiver receiver, MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver) {
+ return read_content_core(strm, last_connection, req, res, receiver,
+ multipart_header, multipart_receiver);
+}
+
+inline bool Server::read_content_core(Stream &strm, bool last_connection,
+ Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader mulitpart_header,
+ ContentReceiver multipart_receiver) {
+ detail::MultipartFormDataParser multipart_form_data_parser;
+ ContentReceiver out;
+
+ if (req.is_multipart_form_data()) {
+ const auto &content_type = req.get_header_value("Content-Type");
std::string boundary;
- if (!detail::parse_multipart_boundary(content_type, boundary) ||
- !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
+ if (!detail::parse_multipart_boundary(content_type, boundary)) {
res.status = 400;
return write_response(strm, last_connection, req, res);
}
- }
- return true;
-}
+ multipart_form_data_parser.set_boundary(boundary);
+ out = [&](const char *buf, size_t n) {
+ return multipart_form_data_parser.parse(buf, n, multipart_receiver,
+ mulitpart_header);
+ };
+ } else {
+ out = receiver;
+ }
-inline bool
-Server::read_content_with_content_receiver(Stream &strm, bool last_connection,
- Request &req, Response &res,
- ContentReceiver receiver) {
- if (!detail::read_content(
- strm, req, payload_max_length_, res.status, Progress(),
- [&](const char *buf, size_t n) { return receiver(buf, n); })) {
+ if (!detail::read_content(strm, req, payload_max_length_, res.status,
+ Progress(), out)) {
return write_response(strm, last_connection, req, res);
}
+ if (req.is_multipart_form_data()) {
+ if (!multipart_form_data_parser.is_valid()) {
+ res.status = 400;
+ return write_response(strm, last_connection, req, res);
+ }
+ }
+
return true;
}
inline bool Server::handle_file_request(Request &req, Response &res) {
- if (!base_dir_.empty() && detail::is_valid_path(req.path)) {
- std::string path = base_dir_ + req.path;
-
- if (!path.empty() && path.back() == '/') { path += "index.html"; }
-
- if (detail::is_file(path)) {
- detail::read_file(path, res.body);
- auto type = detail::find_content_type(path);
- if (type) { res.set_header("Content-Type", type); }
- res.status = 200;
- if (file_request_handler_) { file_request_handler_(req, res); }
- return true;
+ for (const auto &kv : base_dirs_) {
+ const auto &mount_point = kv.first;
+ const auto &base_dir = kv.second;
+
+ // Prefix match
+ if (!req.path.find(mount_point)) {
+ std::string sub_path = "/" + req.path.substr(mount_point.size());
+ if (detail::is_valid_path(sub_path)) {
+ auto path = base_dir + sub_path;
+ if (path.back() == '/') { path += "index.html"; }
+
+ if (detail::is_file(path)) {
+ detail::read_file(path, res.body);
+ auto type = detail::find_content_type(path);
+ if (type) { res.set_header("Content-Type", type); }
+ res.status = 200;
+ if (file_request_handler_) { file_request_handler_(req, res); }
+ return true;
+ }
+ }
}
}
-
return false;
}
@@ -2734,7 +3136,8 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) {
if (address.ss_family == AF_INET) {
return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
} else if (address.ss_family == AF_INET6) {
- return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
+ return ntohs(
+ reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
} else {
return -1;
}
@@ -2790,39 +3193,44 @@ inline bool Server::listen_internal() {
return ret;
}
-inline bool Server::routing(Request &req, Response &res, Stream &strm, bool last_connection) {
+inline bool Server::routing(Request &req, Response &res, Stream &strm,
+ bool last_connection) {
// File handler
if (req.method == "GET" && handle_file_request(req, res)) { return true; }
- // Content reader handler
- if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
- ContentReader content_reader = [&](ContentReceiver receiver) {
- return read_content_with_content_receiver(strm, last_connection, req, res, receiver);
- };
+ if (detail::expect_content(req)) {
+ // Content reader handler
+ {
+ ContentReader reader(
+ [&](ContentReceiver receiver) {
+ return read_content_with_content_receiver(
+ strm, last_connection, req, res, receiver, nullptr, nullptr);
+ },
+ [&](MultipartContentHeader header, ContentReceiver receiver) {
+ return read_content_with_content_receiver(
+ strm, last_connection, req, res, nullptr, header, receiver);
+ });
- if (req.method == "POST") {
- if (dispatch_request_for_content_reader(req, res, content_reader,
- post_handlers_for_content_reader)) {
- return true;
- }
- } else if (req.method == "PUT") {
- if (dispatch_request_for_content_reader(req, res, content_reader,
- put_handlers_for_content_reader)) {
- return true;
- }
- } else if (req.method == "PATCH") {
- if (dispatch_request_for_content_reader(
- req, res, content_reader, patch_handlers_for_content_reader)) {
- return true;
+ if (req.method == "POST") {
+ if (dispatch_request_for_content_reader(
+ req, res, reader, post_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "PUT") {
+ if (dispatch_request_for_content_reader(
+ req, res, reader, put_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "PATCH") {
+ if (dispatch_request_for_content_reader(
+ req, res, reader, patch_handlers_for_content_reader_)) {
+ return true;
+ }
}
}
- }
- // Read content into `req.body`
- if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "PRI") {
- if (!read_content(strm, last_connection, req, res)) {
- return false;
- }
+ // Read content into `req.body`
+ if (!read_content(strm, last_connection, req, res)) { return false; }
}
// Regular handler
@@ -2877,7 +3285,7 @@ Server::dispatch_request_for_content_reader(Request &req, Response &res,
inline bool
Server::process_request(Stream &strm, bool last_connection,
bool &connection_close,
- const std::function<void(Request &)>& setup_request) {
+ const std::function<void(Request &)> &setup_request) {
std::array<char, 2048> buf{};
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
@@ -2953,29 +3361,15 @@ inline Client::Client(const char *host, int port, time_t timeout_sec)
keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
- follow_location_(false) {}
+ follow_location_(false), compress_(false) {}
inline Client::~Client() {}
inline bool Client::is_valid() const { return true; }
inline socket_t Client::create_client_socket() const {
- return detail::create_socket(
- host_.c_str(), port_, [=](socket_t sock, struct addrinfo &ai) -> bool {
- detail::set_nonblocking(sock, true);
-
- auto ret = connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
- if (ret < 0) {
- if (detail::is_connection_error() ||
- !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) {
- detail::close_socket(sock);
- return false;
- }
- }
-
- detail::set_nonblocking(sock, false);
- return true;
- });
+ return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
+ interface_);
}
inline bool Client::read_response_line(Stream &strm, Response &res) {
@@ -3012,6 +3406,43 @@ inline bool Client::send(const Request &req, Response &res) {
ret = redirect(req, res);
}
+ if (ret && !username_.empty() && !password_.empty() && res.status == 401) {
+ int type;
+ std::map<std::string, std::string> digest_auth;
+
+ if ((type = parse_www_authenticate(res, digest_auth)) > 0) {
+ std::pair<std::string, std::string> header;
+
+ if (type == 1) {
+ header = make_basic_authentication_header(username_, password_);
+ } else if (type == 2) {
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ size_t cnonce_count = 1;
+ auto cnonce = random_string(10);
+
+ header = make_digest_authentication_header(
+ req, digest_auth, cnonce_count, cnonce, username_, password_);
+#endif
+ }
+
+ Request new_req;
+ new_req.method = req.method;
+ new_req.path = req.path;
+ new_req.headers = req.headers;
+ new_req.body = req.body;
+ new_req.response_handler = req.response_handler;
+ new_req.content_receiver = req.content_receiver;
+ new_req.progress = req.progress;
+
+ new_req.headers.insert(header);
+
+ Response new_res;
+ auto ret = send(new_req, new_res);
+ if (ret) { res = new_res; }
+ return ret;
+ }
+ }
+
return ret;
}
@@ -3056,46 +3487,51 @@ inline bool Client::redirect(const Request &req, Response &res) {
auto location = res.get_header_value("location");
if (location.empty()) { return false; }
- std::regex re(
+ const static std::regex re(
R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+ std::smatch m;
+ if (!regex_match(location, m, re)) { return false; }
+
+ auto next_scheme = m[1].str();
+ auto next_host = m[2].str();
+ auto next_path = m[3].str();
+ if (next_host.empty()) { next_host = host_; }
+ if (next_path.empty()) { next_path = "/"; }
+
auto scheme = is_ssl() ? "https" : "http";
- std::smatch m;
- if (regex_match(location, m, re)) {
- auto next_scheme = m[1].str();
- auto next_host = m[2].str();
- auto next_path = m[3].str();
- if (next_host.empty()) { next_host = host_; }
- if (next_path.empty()) { next_path = "/"; }
-
- if (next_scheme == scheme && next_host == host_) {
- return detail::redirect(*this, req, res, next_path);
- } else {
- if (next_scheme == "https") {
+ if (next_scheme == scheme && next_host == host_) {
+ return detail::redirect(*this, req, res, next_path);
+ } else {
+ if (next_scheme == "https") {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- SSLClient cli(next_host.c_str());
- cli.follow_location(true);
- return detail::redirect(cli, req, res, next_path);
+ SSLClient cli(next_host.c_str());
+ cli.set_follow_location(true);
+ return detail::redirect(cli, req, res, next_path);
#else
- return false;
+ return false;
#endif
- } else {
- Client cli(next_host.c_str());
- cli.follow_location(true);
- return detail::redirect(cli, req, res, next_path);
- }
+ } else {
+ Client cli(next_host.c_str());
+ cli.set_follow_location(true);
+ return detail::redirect(cli, req, res, next_path);
}
}
- return false;
}
-inline void Client::write_request(Stream &strm, const Request &req,
+inline bool Client::write_request(Stream &strm, const Request &req,
bool last_connection) {
BufferStream bstrm;
// Request line
- auto path = detail::encode_url(req.path);
+ const static std::regex re(
+ R"(^([^:/?#]+://[^/?#]*)?([^?#]*(?:\?[^#]*)?(?:#.*)?))");
+
+ std::smatch m;
+ if (!regex_match(req.path, m, re)) { return false; }
+
+ auto path = m[1].str() + detail::encode_url(m[2].str());
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
@@ -3165,16 +3601,14 @@ inline void Client::write_request(Stream &strm, const Request &req,
} else {
strm.write(req.body);
}
+
+ return true;
}
inline std::shared_ptr<Response> Client::send_with_content_provider(
const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length,
- ContentProvider content_provider, const char *content_type, bool compress) {
-#ifndef CPPHTTPLIB_ZLIB_SUPPORT
- (void)compress;
-#endif
-
+ ContentProvider content_provider, const char *content_type) {
Request req;
req.method = method;
req.headers = headers;
@@ -3183,7 +3617,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
req.headers.emplace("Content-Type", content_type);
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- if (compress) {
+ if (compress_) {
if (content_provider) {
size_t offset = 0;
while (offset < content_length) {
@@ -3219,7 +3653,7 @@ inline bool Client::process_request(Stream &strm, const Request &req,
Response &res, bool last_connection,
bool &connection_close) {
// Send request
- write_request(strm, req, last_connection);
+ if (!write_request(strm, req, last_connection)) { return false; }
// Receive response and headers
if (!read_response_line(strm, res) ||
@@ -3332,7 +3766,8 @@ inline std::shared_ptr<Response> Client::Get(const char *path,
ResponseHandler response_handler,
ContentReceiver content_receiver) {
Progress dummy;
- return Get(path, headers, std::move(response_handler), content_receiver, dummy);
+ return Get(path, headers, std::move(response_handler), content_receiver,
+ dummy);
}
inline std::shared_ptr<Response> Client::Get(const char *path,
@@ -3370,45 +3805,40 @@ inline std::shared_ptr<Response> Client::Head(const char *path,
inline std::shared_ptr<Response> Client::Post(const char *path,
const std::string &body,
- const char *content_type,
- bool compress) {
- return Post(path, Headers(), body, content_type, compress);
+ const char *content_type) {
+ return Post(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers, const std::string &body,
- const char *content_type, bool compress) {
+inline std::shared_ptr<Response> Client::Post(const char *path,
+ const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
return send_with_content_provider("POST", path, headers, body, 0, nullptr,
- content_type, compress);
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Params &params, bool compress) {
- return Post(path, Headers(), params, compress);
+inline std::shared_ptr<Response> Client::Post(const char *path,
+ const Params &params) {
+ return Post(path, Headers(), params);
}
inline std::shared_ptr<Response> Client::Post(const char *path,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress) {
- return Post(path, Headers(), content_length, content_provider, content_type,
- compress);
+ const char *content_type) {
+ return Post(path, Headers(), content_length, content_provider, content_type);
}
inline std::shared_ptr<Response>
Client::Post(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type,
- bool compress) {
+ ContentProvider content_provider, const char *content_type) {
return send_with_content_provider("POST", path, headers, std::string(),
content_length, content_provider,
- content_type, compress);
+ content_type);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const Headers &headers,
- const Params &params,
- bool compress) {
+inline std::shared_ptr<Response>
+Client::Post(const char *path, const Headers &headers, const Params &params) {
std::string query;
for (auto it = params.begin(); it != params.end(); ++it) {
if (it != params.begin()) { query += "&"; }
@@ -3417,19 +3847,17 @@ inline std::shared_ptr<Response> Client::Post(const char *path,
query += detail::encode_url(it->second);
}
- return Post(path, headers, query, "application/x-www-form-urlencoded",
- compress);
+ return Post(path, headers, query, "application/x-www-form-urlencoded");
}
inline std::shared_ptr<Response>
-Client::Post(const char *path, const MultipartFormDataItems &items,
- bool compress) {
- return Post(path, Headers(), items, compress);
+Client::Post(const char *path, const MultipartFormDataItems &items) {
+ return Post(path, Headers(), items);
}
inline std::shared_ptr<Response>
Client::Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items, bool compress) {
+ const MultipartFormDataItems &items) {
auto boundary = detail::make_multipart_data_boundary();
std::string body;
@@ -3451,71 +3879,65 @@ Client::Post(const char *path, const Headers &headers,
body += "--" + boundary + "--\r\n";
std::string content_type = "multipart/form-data; boundary=" + boundary;
- return Post(path, headers, body, content_type.c_str(), compress);
+ return Post(path, headers, body, content_type.c_str());
}
inline std::shared_ptr<Response> Client::Put(const char *path,
const std::string &body,
- const char *content_type,
- bool compress) {
- return Put(path, Headers(), body, content_type, compress);
+ const char *content_type) {
+ return Put(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response>
-Client::Put(const char *path, const Headers &headers, const std::string &body,
- const char *content_type, bool compress) {
+inline std::shared_ptr<Response> Client::Put(const char *path,
+ const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
- content_type, compress);
+ content_type);
}
inline std::shared_ptr<Response> Client::Put(const char *path,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress) {
- return Put(path, Headers(), content_length, content_provider, content_type,
- compress);
+ const char *content_type) {
+ return Put(path, Headers(), content_length, content_provider, content_type);
}
inline std::shared_ptr<Response>
Client::Put(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type,
- bool compress) {
+ ContentProvider content_provider, const char *content_type) {
return send_with_content_provider("PUT", path, headers, std::string(),
content_length, content_provider,
- content_type, compress);
+ content_type);
}
inline std::shared_ptr<Response> Client::Patch(const char *path,
const std::string &body,
- const char *content_type,
- bool compress) {
- return Patch(path, Headers(), body, content_type, compress);
+ const char *content_type) {
+ return Patch(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response>
-Client::Patch(const char *path, const Headers &headers, const std::string &body,
- const char *content_type, bool compress) {
+inline std::shared_ptr<Response> Client::Patch(const char *path,
+ const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
- content_type, compress);
+ content_type);
}
inline std::shared_ptr<Response> Client::Patch(const char *path,
size_t content_length,
ContentProvider content_provider,
- const char *content_type,
- bool compress) {
- return Patch(path, Headers(), content_length, content_provider, content_type,
- compress);
+ const char *content_type) {
+ return Patch(path, Headers(), content_length, content_provider, content_type);
}
inline std::shared_ptr<Response>
Client::Patch(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type,
- bool compress) {
+ ContentProvider content_provider, const char *content_type) {
return send_with_content_provider("PATCH", path, headers, std::string(),
content_length, content_provider,
- content_type, compress);
+ content_type);
}
inline std::shared_ptr<Response> Client::Delete(const char *path) {
@@ -3575,7 +3997,16 @@ inline void Client::set_read_timeout(time_t sec, time_t usec) {
read_timeout_usec_ = usec;
}
-inline void Client::follow_location(bool on) { follow_location_ = on; }
+inline void Client::set_auth(const char *username, const char *password) {
+ username_ = username;
+ password_ = password;
+}
+
+inline void Client::set_follow_location(bool on) { follow_location_ = on; }
+
+inline void Client::set_compress(bool on) { compress_ = on; }
+
+inline void Client::set_interface(const char *intf) { interface_ = intf; }
/*
* SSL Implementation
@@ -4021,6 +4452,8 @@ inline bool SSLClient::check_host_name(const char *pattern,
}
#endif
+// ----------------------------------------------------------------------------
+
} // namespace httplib
#endif // CPPHTTPLIB_HTTPLIB_H