mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5d4c143e5 | |||
| c0c36f021d | |||
| 8e8a23e3d2 | |||
| 890a2dd85d | |||
| ca5fe354fb | |||
| dd98d2a24d | |||
| 1f110b54d8 | |||
| 7b6867bcdf | |||
| 8b28577ec6 | |||
| 55b38907dc | |||
| 53ea9e8bb4 |
@@ -871,7 +871,7 @@ auto res = cli.Post(
|
||||
httplib::Client cli(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
auto res = cli.Get("/", [](size_t len, size_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)(len*100/total));
|
||||
@@ -990,6 +990,25 @@ auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths
|
||||
- `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters
|
||||
- `false`: Sends paths as-is without encoding (useful for pre-encoded URLs)
|
||||
|
||||
### Performance Note for Local Connections
|
||||
|
||||
> [!WARNING]
|
||||
> On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost".
|
||||
>
|
||||
> See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264
|
||||
|
||||
```cpp
|
||||
// May be slower on Windows due to DNS resolution delays
|
||||
httplib::Client cli("localhost", 8080);
|
||||
httplib::Server svr;
|
||||
svr.listen("localhost", 8080);
|
||||
|
||||
// Faster alternative for local connections
|
||||
httplib::Client cli("127.0.0.1", 8080);
|
||||
httplib::Server svr;
|
||||
svr.listen("127.0.0.1", 8080);
|
||||
```
|
||||
|
||||
Compression
|
||||
-----------
|
||||
|
||||
@@ -1065,6 +1084,8 @@ cli.set_address_family(AF_UNIX);
|
||||
|
||||
"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
|
||||
|
||||
This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior:
|
||||
|
||||
|
||||
URI Encoding/Decoding Utilities
|
||||
-------------------------------
|
||||
@@ -1141,6 +1162,24 @@ Serving HTTP on 0.0.0.0 port 80 ...
|
||||
NOTE
|
||||
----
|
||||
|
||||
### Regular Expression Stack Overflow
|
||||
|
||||
> [!CAUTION]
|
||||
> When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method.
|
||||
>
|
||||
> ```cpp
|
||||
> // This pattern can cause stack overflow with large input
|
||||
> svr.Get(".*", handler);
|
||||
> ```
|
||||
>
|
||||
> Consider using simpler patterns or path parameters to avoid this issue:
|
||||
>
|
||||
> ```cpp
|
||||
> // Safer alternatives
|
||||
> svr.Get("/users/:id", handler); // Path parameters
|
||||
> svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns
|
||||
> ```
|
||||
|
||||
### g++
|
||||
|
||||
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.23.0"
|
||||
#define CPPHTTPLIB_VERSION "0.24.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x001800"
|
||||
|
||||
/*
|
||||
* Platform compatibility check
|
||||
@@ -18,10 +19,10 @@
|
||||
#error \
|
||||
"cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler."
|
||||
#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8
|
||||
#error \
|
||||
#warning \
|
||||
"cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler."
|
||||
#elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8
|
||||
#error \
|
||||
#warning \
|
||||
"cpp-httplib doesn't support platforms where size_t is less than 64 bits."
|
||||
#endif
|
||||
|
||||
@@ -2029,7 +2030,7 @@ inline size_t get_header_value_u64(const Headers &headers,
|
||||
inline size_t get_header_value_u64(const Headers &headers,
|
||||
const std::string &key, size_t def,
|
||||
size_t id) {
|
||||
bool dummy = false;
|
||||
auto dummy = false;
|
||||
return get_header_value_u64(headers, key, def, id, dummy);
|
||||
}
|
||||
|
||||
@@ -2281,6 +2282,10 @@ Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_write_timeout(duration);
|
||||
}
|
||||
|
||||
inline void Client::set_max_timeout(time_t msec) {
|
||||
cli_->set_max_timeout(msec);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void
|
||||
Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -2296,15 +2301,19 @@ std::string hosted_at(const std::string &hostname);
|
||||
|
||||
void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
|
||||
|
||||
// JavaScript-style URL encoding/decoding functions
|
||||
std::string encode_uri_component(const std::string &value);
|
||||
|
||||
std::string encode_uri(const std::string &value);
|
||||
|
||||
std::string decode_uri_component(const std::string &value);
|
||||
|
||||
std::string decode_uri(const std::string &value);
|
||||
|
||||
std::string encode_query_param(const std::string &value);
|
||||
// RFC 3986 compliant URL component encoding/decoding functions
|
||||
std::string encode_path_component(const std::string &component);
|
||||
std::string decode_path_component(const std::string &component);
|
||||
std::string encode_query_component(const std::string &component,
|
||||
bool space_as_plus = true);
|
||||
std::string decode_query_component(const std::string &component,
|
||||
bool plus_as_space = true);
|
||||
|
||||
std::string append_query_params(const std::string &path, const Params ¶ms);
|
||||
|
||||
@@ -2347,8 +2356,6 @@ private:
|
||||
int ret_ = -1;
|
||||
};
|
||||
|
||||
std::string decode_path(const std::string &s, bool convert_plus_to_space);
|
||||
|
||||
std::string trim_copy(const std::string &s);
|
||||
|
||||
void divide(
|
||||
@@ -2849,43 +2856,6 @@ inline std::string encode_path(const std::string &s) {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string decode_path(const std::string &s,
|
||||
bool convert_plus_to_space) {
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
if (s[i] == '%' && i + 1 < s.size()) {
|
||||
if (s[i + 1] == 'u') {
|
||||
auto val = 0;
|
||||
if (from_hex_to_i(s, i + 2, 4, val)) {
|
||||
// 4 digits Unicode codes
|
||||
char buff[4];
|
||||
size_t len = to_utf8(val, buff);
|
||||
if (len > 0) { result.append(buff, len); }
|
||||
i += 5; // 'u0000'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
} else {
|
||||
auto val = 0;
|
||||
if (from_hex_to_i(s, i + 1, 2, val)) {
|
||||
// 2 digits hex codes
|
||||
result += static_cast<char>(val);
|
||||
i += 2; // '00'
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
} else if (convert_plus_to_space && s[i] == '+') {
|
||||
result += ' ';
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string file_extension(const std::string &path) {
|
||||
std::smatch m;
|
||||
thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
|
||||
@@ -3215,6 +3185,23 @@ inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
|
||||
template <bool Read>
|
||||
inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef __APPLE__
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
|
||||
fd_set fds, *rfds, *wfds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
rfds = (Read ? &fds : nullptr);
|
||||
wfds = (Read ? nullptr : &fds);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
|
||||
});
|
||||
#else
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sock;
|
||||
pfd.events = (Read ? POLLIN : POLLOUT);
|
||||
@@ -3222,6 +3209,7 @@ inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
@@ -3234,6 +3222,36 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
time_t usec) {
|
||||
#ifdef __APPLE__
|
||||
if (sock >= FD_SETSIZE) { return Error::Connection; }
|
||||
|
||||
fd_set fdsr, fdsw;
|
||||
FD_ZERO(&fdsr);
|
||||
FD_ZERO(&fdsw);
|
||||
FD_SET(sock, &fdsr);
|
||||
FD_SET(sock, &fdsw);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
auto ret = handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), &fdsr, &fdsw, nullptr, &tv);
|
||||
});
|
||||
|
||||
if (ret == 0) { return Error::ConnectionTimeout; }
|
||||
|
||||
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
auto error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
auto successful = res >= 0 && !error;
|
||||
return successful ? Error::Success : Error::Connection;
|
||||
}
|
||||
|
||||
return Error::Connection;
|
||||
#else
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
pfd_read.events = POLLIN | POLLOUT;
|
||||
@@ -3255,6 +3273,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
}
|
||||
|
||||
return Error::Connection;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool is_socket_alive(socket_t sock) {
|
||||
@@ -4561,7 +4580,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
case_ignore::equal(key, "Referer")) {
|
||||
fn(key, val);
|
||||
} else {
|
||||
fn(key, decode_path(val, false));
|
||||
fn(key, decode_path_component(val));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -5209,9 +5228,9 @@ inline std::string params_to_query_str(const Params ¶ms) {
|
||||
|
||||
for (auto it = params.begin(); it != params.end(); ++it) {
|
||||
if (it != params.begin()) { query += "&"; }
|
||||
query += it->first;
|
||||
query += encode_query_component(it->first);
|
||||
query += "=";
|
||||
query += httplib::encode_uri_component(it->second);
|
||||
query += encode_query_component(it->second);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
@@ -5234,7 +5253,7 @@ inline void parse_query_text(const char *data, std::size_t size,
|
||||
});
|
||||
|
||||
if (!key.empty()) {
|
||||
params.emplace(decode_path(key, true), decode_path(val, true));
|
||||
params.emplace(decode_query_component(key), decode_query_component(val));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5386,17 +5405,30 @@ inline bool parse_accept_header(const std::string &s,
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
accept_entry.quality = std::stod(quality_str);
|
||||
// Check if quality is in valid range [0.0, 1.0]
|
||||
if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
{
|
||||
std::istringstream iss(quality_str);
|
||||
iss >> accept_entry.quality;
|
||||
|
||||
// Check if conversion was successful and entire string was consumed
|
||||
if (iss.fail() || !iss.eof()) {
|
||||
has_invalid_entry = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
try {
|
||||
accept_entry.quality = std::stod(quality_str);
|
||||
} catch (...) {
|
||||
has_invalid_entry = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// Check if quality is in valid range [0.0, 1.0]
|
||||
if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
|
||||
has_invalid_entry = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// No quality parameter, use entire entry as media type
|
||||
accept_entry.media_type = entry;
|
||||
@@ -5544,7 +5576,7 @@ public:
|
||||
|
||||
std::smatch m2;
|
||||
if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
|
||||
file_.filename = decode_path(m2[1], false); // override...
|
||||
file_.filename = decode_path_component(m2[1]); // override...
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
@@ -6450,9 +6482,154 @@ inline std::string decode_uri(const std::string &value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[deprecated("Use encode_uri_component instead")]]
|
||||
inline std::string encode_query_param(const std::string &value) {
|
||||
return encode_uri_component(value);
|
||||
inline std::string encode_path_component(const std::string &component) {
|
||||
std::string result;
|
||||
result.reserve(component.size() * 3);
|
||||
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
auto c = static_cast<unsigned char>(component[i]);
|
||||
|
||||
// Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
|
||||
// "," / ";" / "="
|
||||
else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
|
||||
c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
|
||||
c == '=') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Colon is allowed in path segments except first segment
|
||||
else if (c == ':') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// @ is allowed in path
|
||||
else if (c == '@') {
|
||||
result += static_cast<char>(c);
|
||||
} else {
|
||||
result += '%';
|
||||
char hex[3];
|
||||
snprintf(hex, sizeof(hex), "%02X", c);
|
||||
result.append(hex, 2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string decode_path_component(const std::string &component) {
|
||||
std::string result;
|
||||
result.reserve(component.size());
|
||||
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
if (component[i] == '%' && i + 1 < component.size()) {
|
||||
if (component[i + 1] == 'u') {
|
||||
// Unicode %uXXXX encoding
|
||||
auto val = 0;
|
||||
if (detail::from_hex_to_i(component, i + 2, 4, val)) {
|
||||
// 4 digits Unicode codes
|
||||
char buff[4];
|
||||
size_t len = detail::to_utf8(val, buff);
|
||||
if (len > 0) { result.append(buff, len); }
|
||||
i += 5; // 'u0000'
|
||||
} else {
|
||||
result += component[i];
|
||||
}
|
||||
} else {
|
||||
// Standard %XX encoding
|
||||
auto val = 0;
|
||||
if (detail::from_hex_to_i(component, i + 1, 2, val)) {
|
||||
// 2 digits hex codes
|
||||
result += static_cast<char>(val);
|
||||
i += 2; // 'XX'
|
||||
} else {
|
||||
result += component[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += component[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string encode_query_component(const std::string &component,
|
||||
bool space_as_plus) {
|
||||
std::string result;
|
||||
result.reserve(component.size() * 3);
|
||||
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
auto c = static_cast<unsigned char>(component[i]);
|
||||
|
||||
// Unreserved characters per RFC 3986
|
||||
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Space handling
|
||||
else if (c == ' ') {
|
||||
if (space_as_plus) {
|
||||
result += '+';
|
||||
} else {
|
||||
result += "%20";
|
||||
}
|
||||
}
|
||||
// Plus sign handling
|
||||
else if (c == '+') {
|
||||
if (space_as_plus) {
|
||||
result += "%2B";
|
||||
} else {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
}
|
||||
// Query-safe sub-delimiters (excluding & and = which are query delimiters)
|
||||
else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
|
||||
c == '*' || c == ',' || c == ';') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Colon and @ are allowed in query
|
||||
else if (c == ':' || c == '@') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Forward slash is allowed in query values
|
||||
else if (c == '/') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Question mark is allowed in query values (after first ?)
|
||||
else if (c == '?') {
|
||||
result += static_cast<char>(c);
|
||||
} else {
|
||||
result += '%';
|
||||
char hex[3];
|
||||
snprintf(hex, sizeof(hex), "%02X", c);
|
||||
result.append(hex, 2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string decode_query_component(const std::string &component,
|
||||
bool plus_as_space) {
|
||||
std::string result;
|
||||
result.reserve(component.size());
|
||||
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
if (component[i] == '%' && i + 2 < component.size()) {
|
||||
std::string hex = component.substr(i + 1, 2);
|
||||
char *end;
|
||||
unsigned long value = std::strtoul(hex.c_str(), &end, 16);
|
||||
if (end == hex.c_str() + 2) {
|
||||
result += static_cast<char>(value);
|
||||
i += 2;
|
||||
} else {
|
||||
result += component[i];
|
||||
}
|
||||
} else if (component[i] == '+' && plus_as_space) {
|
||||
result += ' '; // + becomes space in form-urlencoded
|
||||
} else {
|
||||
result += component[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string append_query_params(const std::string &path,
|
||||
@@ -7337,8 +7514,8 @@ inline bool Server::parse_request_line(const char *s, Request &req) const {
|
||||
detail::divide(req.target, '?',
|
||||
[&](const char *lhs_data, std::size_t lhs_size,
|
||||
const char *rhs_data, std::size_t rhs_size) {
|
||||
req.path = detail::decode_path(
|
||||
std::string(lhs_data, lhs_size), false);
|
||||
req.path =
|
||||
decode_path_component(std::string(lhs_data, lhs_size));
|
||||
detail::parse_query_text(rhs_data, rhs_size, req.params);
|
||||
});
|
||||
}
|
||||
@@ -8001,6 +8178,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
res.version = "HTTP/1.1";
|
||||
res.headers = default_headers_;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Socket file descriptor exceeded FD_SETSIZE...
|
||||
if (strm.socket() >= FD_SETSIZE) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Request line and headers
|
||||
if (!parse_request_line(line_reader.ptr(), req) ||
|
||||
!detail::read_headers(strm, req.headers)) {
|
||||
@@ -8601,7 +8788,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
auto path = detail::decode_path(next_path, true) + next_query;
|
||||
auto path = decode_query_component(next_path, true) + next_query;
|
||||
|
||||
// Same host redirect - use current client
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
@@ -8785,7 +8972,11 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
}
|
||||
|
||||
if (!req.has_header("Host")) {
|
||||
if (is_ssl()) {
|
||||
// For Unix socket connections, use "localhost" as Host header (similar to
|
||||
// curl behavior)
|
||||
if (address_family_ == AF_UNIX) {
|
||||
req.set_header("Host", "localhost");
|
||||
} else if (is_ssl()) {
|
||||
if (port_ == 443) {
|
||||
req.set_header("Host", host_);
|
||||
} else {
|
||||
@@ -8885,15 +9076,28 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path_with_query =
|
||||
req.params.empty() ? req.path
|
||||
: append_query_params(req.path, req.params);
|
||||
// Extract path and query from req.path
|
||||
std::string path_part, query_part;
|
||||
auto query_pos = req.path.find('?');
|
||||
if (query_pos != std::string::npos) {
|
||||
path_part = req.path.substr(0, query_pos);
|
||||
query_part = req.path.substr(query_pos + 1);
|
||||
} else {
|
||||
path_part = req.path;
|
||||
query_part = "";
|
||||
}
|
||||
|
||||
const auto &path =
|
||||
path_encode_ ? detail::encode_path(path_with_query) : path_with_query;
|
||||
// Encode path and query
|
||||
auto path_with_query =
|
||||
path_encode_ ? detail::encode_path(path_part) : path_part;
|
||||
|
||||
detail::write_request_line(bstrm, req.method, path);
|
||||
detail::parse_query_text(query_part, req.params);
|
||||
if (!req.params.empty()) {
|
||||
path_with_query = append_query_params(path_with_query, req.params);
|
||||
}
|
||||
|
||||
// Write request line and headers
|
||||
detail::write_request_line(bstrm, req.method, path_with_query);
|
||||
header_writer_(bstrm, req.headers);
|
||||
|
||||
// Flush buffer
|
||||
|
||||
@@ -18,6 +18,14 @@ project(
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
if cxx.sizeof('void *') != 8
|
||||
if host_machine.system() == 'windows'
|
||||
error('unsupported architecture: cpp-httplib doesn\'t support 32-bit Windows. Please use a 64-bit compiler.')
|
||||
else
|
||||
warning('cpp-httplib doesn\'t support 32-bit platforms. Please use a 64-bit compiler.')
|
||||
endif
|
||||
endif
|
||||
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
|
||||
+90
-4
@@ -173,6 +173,48 @@ TEST_F(UnixSocketTest, abstract) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(UnixSocketTest, HostHeaderAutoSet) {
|
||||
httplib::Server svr;
|
||||
std::string received_host_header;
|
||||
|
||||
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
|
||||
// Capture the Host header sent by the client
|
||||
auto host_iter = req.headers.find("Host");
|
||||
if (host_iter != req.headers.end()) {
|
||||
received_host_header = host_iter->second;
|
||||
}
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
// Test that Host header is automatically set to "localhost" for Unix socket
|
||||
// connections
|
||||
httplib::Client cli{pathname_};
|
||||
cli.set_address_family(AF_UNIX);
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
|
||||
const auto &result = cli.Get(pattern_);
|
||||
ASSERT_TRUE(result) << "error: " << result.error();
|
||||
|
||||
const auto &resp = result.value();
|
||||
EXPECT_EQ(resp.status, StatusCode::OK_200);
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
|
||||
// Verify that Host header was automatically set to "localhost"
|
||||
EXPECT_EQ(received_host_header, "localhost");
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST(SocketStream, wait_writable_UNIX) {
|
||||
int fds[2];
|
||||
@@ -259,9 +301,8 @@ TEST(StartupTest, WSAStartup) {
|
||||
|
||||
TEST(DecodePathTest, PercentCharacter) {
|
||||
EXPECT_EQ(
|
||||
detail::decode_path(
|
||||
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)",
|
||||
false),
|
||||
decode_path_component(
|
||||
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)"),
|
||||
u8"descrip=Gastos áéíóúñÑ 6");
|
||||
}
|
||||
|
||||
@@ -271,7 +312,7 @@ TEST(DecodePathTest, PercentCharacterNUL) {
|
||||
expected.push_back('\0');
|
||||
expected.push_back('x');
|
||||
|
||||
EXPECT_EQ(detail::decode_path("x%00x", false), expected);
|
||||
EXPECT_EQ(decode_path_component("x%00x"), expected);
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||
@@ -9900,6 +9941,51 @@ TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RedirectTest, RedirectToUrlWithPlusInQueryParameters) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request & /*req*/, Response &res) {
|
||||
res.set_redirect(R"(/hello?key=AByz09+~-._%20%26%3F%C3%BC%2B)");
|
||||
});
|
||||
|
||||
svr.Get("/hello", [](const Request &req, Response &res) {
|
||||
res.set_content(req.get_param_value("key"), "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("AByz09 ~-._ &?ü+", res->body);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(RedirectTest, Issue2185_Online) {
|
||||
SSLClient client("github.com");
|
||||
client.set_follow_location(true);
|
||||
|
||||
auto res = client.Get("/Coollab-Art/Coollab/releases/download/1.1.1_UI-Scale/"
|
||||
"Coollab-Windows.zip");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(9920427U, res->body.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(VulnerabilityTest, CRLFInjection) {
|
||||
Server svr;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user