Compare commits

...

14 Commits

Author SHA1 Message Date
Omkar Jadhav 852a374748 Fix server crash caused due to regex complexity while matching headers. (#632)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.

* Fix server crash caused due to regex complexity while matching headers.

While parsing content-type header in multipart form request the server crashes due to the exhaustion of max iterations performed while matching the input string with content-type regex.
Have removed the regex which might use backtracking while matching and replaced it with manual string processing. Have added tests as well.

* Remove magic number

Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>

Co-authored-by: yhirose <yhirose@users.noreply.github.com>
Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>
2020-09-03 13:17:52 -04:00
Ivan Fefer 3b5bab3308 Fix gzip_decompressor in case of one chunk being exactly equal to buffer size (#636)
* add larget chunks test

* revert test

* Fix gzip decoder in case of chunk being equal to buffer size

* add test
2020-09-03 12:20:02 -04:00
yhirose 69e75f4a67 Fix #635. HTTPS request stucked with proxy (#637) 2020-09-03 12:17:53 -04:00
Omkar Jadhav b0fd4befb1 Fix query parsing issues (#629)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.
2020-08-28 09:43:28 -04:00
yhirose 3e80666a74 Fix #628 2020-08-27 19:45:28 -04:00
yhirose 3e4567bae8 Updated Repl.it examples 2020-08-27 14:16:35 -04:00
yhirose db075d8cf9 Added Repl.it examples 2020-08-27 14:06:57 -04:00
yhirose 16df0ef37e Code cleanup 2020-08-26 12:18:49 -04:00
Ivan Fefer f1a2ac5108 Avoid copying of content provider if possible (#627) 2020-08-26 08:56:51 -04:00
yhirose e5743b358d Updated README 2020-08-25 20:26:53 -04:00
Ivan Fefer 1184bbe4cb Fix typo in README (#625) 2020-08-25 06:38:16 -04:00
Felix Kästner 9dcffda7ae Fix cmake execute_process working directory (#622)
Correct the working directory of the execute_process call, to get the version from git tags.
The working directory should be the the directory of this libary. Otherwise, if you include httplib
as a git submodule and call add_subdirectory, the execute_process command will be run in a wrong
directory leading to error.
2020-08-24 17:23:01 -04:00
yhirose e5903635e2 Fix #619 2020-08-22 12:54:43 -04:00
yhirose 510b4eaaae Fix #613 2020-08-17 13:40:06 -04:00
4 changed files with 294 additions and 128 deletions
+1
View File
@@ -66,6 +66,7 @@ if(Git_FOUND)
# Gets the latest tag as a string like "v0.6.6"
# Can silently fail if git isn't on the system
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE _raw_version_string
ERROR_VARIABLE _git_tag_error
)
+40 -7
View File
@@ -7,8 +7,41 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include **httplib.h** file in your code!
Server Example
--------------
NOTE: This is a 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
Simple examples
---------------
#### Server
```c++
httplib::Server svr;
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("0.0.0.0", 8080);
```
#### Client
```c++
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
auto res = cli.Get("/hi");
res->status; // 200
res->body; // "Hello World!"
```
### Try out the examples on Repl.it!
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
Server
------
```c++
#include <httplib.h>
@@ -197,7 +230,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
// prepare data...
sink.write(data.data(), data.size());
} else {
done(); // No more data
sink.done(); // No more data
}
return true; // return 'false' if you want to cancel the process.
});
@@ -295,8 +328,8 @@ svr.new_task_queue = [] {
};
```
Client Example
--------------
Client
------
```c++
#include <httplib.h>
@@ -564,9 +597,9 @@ NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
SSLServer svr("./cert.pem", "./key.pem");
httplib::SSLServer svr("./cert.pem", "./key.pem");
SSLClient cli("localhost", 8080);
httplib::SSLClient cli("localhost", 1234); // or `httplib::Client cli("https://localhost:1234");`
cli.set_ca_cert_path("./ca-bundle.crt");
cli.enable_server_certificate_verification(true);
```
+166 -120
View File
@@ -281,7 +281,7 @@ public:
private:
class data_sink_streambuf : public std::streambuf {
public:
data_sink_streambuf(DataSink &sink) : sink_(sink) {}
explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
protected:
std::streamsize xsputn(const char *s, std::streamsize n) {
@@ -384,6 +384,7 @@ struct Request {
struct Response {
std::string version;
int status = -1;
std::string reason;
Headers headers;
std::string body;
@@ -402,15 +403,15 @@ struct Response {
void set_content_provider(
size_t length, const char *content_type, ContentProvider provider,
std::function<void()> resource_releaser = [] {});
const std::function<void()> &resource_releaser = nullptr);
void set_content_provider(
const char *content_type, ContentProviderWithoutLength provider,
std::function<void()> resource_releaser = [] {});
const std::function<void()> &resource_releaser = nullptr);
void set_chunked_content_provider(
const char *content_type, ContentProviderWithoutLength provider,
std::function<void()> resource_releaser = [] {});
const std::function<void()> &resource_releaser = nullptr);
Response() = default;
Response(const Response &) = default;
@@ -634,10 +635,11 @@ private:
bool routing(Request &req, Response &res, Stream &strm);
bool handle_file_request(Request &req, Response &res, bool head = false);
bool dispatch_request(Request &req, Response &res, Handlers &handlers);
bool dispatch_request_for_content_reader(Request &req, Response &res,
ContentReader content_reader,
HandlersForContentReader &handlers);
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 parse_request_line(const char *s, Request &req);
bool write_response(Stream &strm, bool close_connection, const Request &req,
@@ -696,14 +698,15 @@ enum Error {
class Result {
public:
Result(std::shared_ptr<Response> res, Error err) : res_(res), err_(err) {}
operator bool() { return res_ != nullptr; }
Result(const std::shared_ptr<Response> &res, Error err)
: res_(res), err_(err) {}
operator bool() const { return res_ != nullptr; }
bool operator==(std::nullptr_t) const { return res_ == nullptr; }
bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
const Response &value() { return *res_; }
const Response &operator*() { return *res_; }
const Response *operator->() { return res_.get(); }
Error error() { return err_; }
const Response &value() const { return *res_; }
const Response &operator*() const { return *res_; }
const Response *operator->() const { return res_.get(); }
Error error() const { return err_; }
private:
std::shared_ptr<Response> res_;
@@ -899,7 +902,7 @@ protected:
std::string interface_;
std::string proxy_host_;
int proxy_port_;
int proxy_port_ = -1;
std::string proxy_basic_auth_username_;
std::string proxy_basic_auth_password_;
@@ -1449,14 +1452,23 @@ inline std::pair<int, int> trim(const char *b, const char *e, int left,
return std::make_pair(left, right);
}
inline void trim(std::string &s) {
auto is_not_space = [](int ch) { return !std::isspace(ch); };
s.erase(s.begin(), std::find_if(s.begin(), s.end(), is_not_space));
s.erase(std::find_if(s.rbegin(), s.rend(), is_not_space).base(), s.end());
}
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
int i = 0;
int beg = 0;
while (e ? (b + i != e) : (b[i] != '\0')) {
while (e ? (b + i < e) : (b[i] != '\0')) {
if (b[i] == d) {
auto r = trim(b, e, beg, i);
fn(&b[r.first], &b[r.second]);
if (r.first < r.second) {
fn(&b[r.first], &b[r.second]);
}
beg = i + 1;
}
i++;
@@ -1464,7 +1476,9 @@ template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
if (i) {
auto r = trim(b, e, beg, i);
fn(&b[r.first], &b[r.second]);
if (r.first < r.second) {
fn(&b[r.first], &b[r.second]);
}
}
}
@@ -1971,7 +1985,7 @@ inline socket_t create_client_socket(const char *host, int port,
});
if (sock != INVALID_SOCKET) {
if (error != Error::Success) { error = Error::Success; }
error = Error::Success;
} else {
if (error == Error::Success) { error = Error::Connection; }
}
@@ -2260,7 +2274,7 @@ public:
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
std::array<char, 16384> buff{};
do {
while (strm_.avail_in > 0) {
strm_.avail_out = buff.size();
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
@@ -2275,7 +2289,7 @@ public:
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
} while (strm_.avail_out == 0);
}
return ret == Z_OK || ret == Z_STREAM_END;
}
@@ -2607,7 +2621,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
Progress progress, ContentReceiver receiver,
bool decompress) {
return prepare_content_receiver(
x, status, receiver, decompress, [&](ContentReceiver &out) {
x, status, receiver, decompress, [&](const ContentReceiver &out) {
auto ret = true;
auto exceed_payload_max_length = false;
@@ -2821,6 +2835,18 @@ inline bool redirect(T &cli, const Request &req, Response &res,
return ret;
}
inline bool contains_header(const std::string &header, const std::string &name) {
if (header.length() >= name.length()) {
for (int i = 0; i < name.length(); ++i) {
if (std::tolower(header[i]) != std::tolower(name[i])) {
return false;
}
}
return true;
}
return false;
}
inline std::string params_to_query_str(const Params &params) {
std::string query;
@@ -2830,12 +2856,11 @@ inline std::string params_to_query_str(const Params &params) {
query += "=";
query += encode_url(it->second);
}
return query;
}
inline void parse_query_text(const std::string &s, Params &params) {
split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
std::string key;
std::string val;
split(b, e, '=', [&](const char *b2, const char *e2) {
@@ -2845,7 +2870,10 @@ inline void parse_query_text(const std::string &s, Params &params) {
val.assign(b2, e2);
}
});
params.emplace(decode_url(key, true), decode_url(val, true));
if(!key.empty()) {
params.emplace(decode_url(key, true), decode_url(val, true));
}
});
}
@@ -2905,8 +2933,6 @@ public:
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="
@@ -2952,8 +2978,11 @@ public:
auto header = buf_.substr(0, pos);
{
std::smatch m;
if (std::regex_match(header, m, re_content_type)) {
file_.content_type = m[1];
const std::string header_name = "content-type:";
if (contains_header(header, header_name)) {
header.erase(header.begin(), header.begin() + header_name.size());
trim(header);
file_.content_type = header;
} else if (std::regex_match(header, m, re_content_disposition)) {
file_.name = m[1];
file_.filename = m[2];
@@ -3019,14 +3048,14 @@ public:
}
case 4: { // Boundary
if (crlf_.size() > buf_.size()) { return true; }
if (buf_.find(crlf_) == 0) {
if (buf_.compare(0, crlf_.size(), 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) {
if (buf_.compare(0, pattern.size(), pattern) == 0) {
buf_.erase(0, pattern.size());
off_ += pattern.size();
is_valid_ = true;
@@ -3314,39 +3343,6 @@ public:
static WSInit wsinit_;
#endif
} // namespace detail
// Header utilities
inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
std::string field = "bytes=";
auto i = 0;
for (auto r : ranges) {
if (i != 0) { field += ", "; }
if (r.first != -1) { field += std::to_string(r.first); }
field += '-';
if (r.second != -1) { field += std::to_string(r.second); }
i++;
}
return std::make_pair("Range", field);
}
inline std::pair<std::string, std::string>
make_basic_authentication_header(const std::string &username,
const std::string &password,
bool is_proxy = false) {
auto field = "Basic " + detail::base64_encode(username + ":" + password);
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
}
inline std::pair<std::string, std::string>
make_bearer_token_authentication_header(const std::string &token,
bool is_proxy = false) {
auto field = "Bearer " + token;
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, 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,
@@ -3444,6 +3440,53 @@ inline std::string random_string(size_t length) {
return str;
}
class ContentProviderAdapter {
public:
explicit ContentProviderAdapter(
ContentProviderWithoutLength &&content_provider)
: content_provider_(content_provider) {}
bool operator()(size_t offset, size_t, DataSink &sink) {
return content_provider_(offset, sink);
}
private:
ContentProviderWithoutLength content_provider_;
};
} // namespace detail
// Header utilities
inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
std::string field = "bytes=";
auto i = 0;
for (auto r : ranges) {
if (i != 0) { field += ", "; }
if (r.first != -1) { field += std::to_string(r.first); }
field += '-';
if (r.second != -1) { field += std::to_string(r.second); }
i++;
}
return std::make_pair("Range", field);
}
inline std::pair<std::string, std::string>
make_basic_authentication_header(const std::string &username,
const std::string &password,
bool is_proxy = false) {
auto field = "Basic " + detail::base64_encode(username + ":" + password);
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
}
inline std::pair<std::string, std::string>
make_bearer_token_authentication_header(const std::string &token,
bool is_proxy = false) {
auto field = "Bearer " + token;
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
}
// Request implementation
inline bool Request::has_header(const char *key) const {
return detail::has_header(headers, key);
@@ -3568,37 +3611,32 @@ inline void Response::set_content(std::string s, const char *content_type) {
inline void
Response::set_content_provider(size_t in_length, const char *content_type,
ContentProvider provider,
std::function<void()> resource_releaser) {
const std::function<void()> &resource_releaser) {
assert(in_length > 0);
set_header("Content-Type", content_type);
content_length_ = in_length;
content_provider_ = [provider](size_t offset, size_t length, DataSink &sink) {
return provider(offset, length, sink);
};
content_provider_ = std::move(provider);
content_provider_resource_releaser_ = resource_releaser;
is_chunked_content_provider = false;
}
inline void Response::set_content_provider(
const char *content_type, ContentProviderWithoutLength provider,
std::function<void()> resource_releaser) {
inline void
Response::set_content_provider(const char *content_type,
ContentProviderWithoutLength provider,
const std::function<void()> &resource_releaser) {
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = [provider](size_t offset, size_t, DataSink &sink) {
return provider(offset, sink);
};
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
content_provider_resource_releaser_ = resource_releaser;
is_chunked_content_provider = false;
}
inline void Response::set_chunked_content_provider(
const char *content_type, ContentProviderWithoutLength provider,
std::function<void()> resource_releaser) {
const std::function<void()> &resource_releaser) {
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = [provider](size_t offset, size_t, DataSink &sink) {
return provider(offset, sink);
};
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
content_provider_resource_releaser_ = resource_releaser;
is_chunked_content_provider = true;
}
@@ -3727,11 +3765,13 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; }
} // namespace detail
// HTTP server implementation
inline Server::Server() : svr_sock_(INVALID_SOCKET), is_running_(false) {
inline Server::Server()
: new_task_queue(
[] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
svr_sock_(INVALID_SOCKET), is_running_(false) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
new_task_queue = [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); };
}
inline Server::~Server() {}
@@ -4233,7 +4273,7 @@ inline bool Server::handle_file_request(Request &req, Response &res,
const auto &base_dir = kv.second;
// Prefix match
if (!req.path.find(mount_point)) {
if (!req.path.compare(0, mount_point.size(), 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;
@@ -4417,7 +4457,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
}
inline bool Server::dispatch_request(Request &req, Response &res,
Handlers &handlers) {
const Handlers &handlers) {
try {
for (const auto &x : handlers) {
@@ -4441,7 +4481,7 @@ inline bool Server::dispatch_request(Request &req, Response &res,
inline bool Server::dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
HandlersForContentReader &handlers) {
const HandlersForContentReader &handlers) {
for (const auto &x : handlers) {
const auto &pattern = x.first;
const auto &handler = x.second;
@@ -4569,7 +4609,7 @@ inline bool ClientImpl::is_valid() const { return true; }
inline Error ClientImpl::get_last_error() const { return error_; }
inline socket_t ClientImpl::create_client_socket() const {
if (!proxy_host_.empty()) {
if (!proxy_host_.empty() && proxy_port_ != -1) {
return detail::create_client_socket(
proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_,
connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
@@ -4602,12 +4642,13 @@ inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
if (!line_reader.getline()) { return false; }
const static std::regex re("(HTTP/1\\.[01]) (\\d+).*?\r\n");
const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
std::cmatch m;
if (std::regex_match(line_reader.ptr(), m, re)) {
res.version = std::string(m[1]);
res.status = std::stoi(std::string(m[2]));
res.reason = std::string(m[3]);
}
return true;
@@ -4632,7 +4673,7 @@ inline bool ClientImpl::send(const Request &req, Response &res) {
// TODO: refactoring
if (is_ssl()) {
auto &scli = static_cast<SSLClient &>(*this);
if (!proxy_host_.empty()) {
if (!proxy_host_.empty() && proxy_port_ != -1) {
bool success = false;
if (!scli.connect_with_proxy(socket_, res, success)) {
return success;
@@ -4669,7 +4710,7 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req,
bool ret;
if (!is_ssl() && !proxy_host_.empty()) {
if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
auto req2 = req;
req2.path = "http://" + host_and_port_ + req.path;
ret = process_request(strm, req2, res, close_connection);
@@ -4694,13 +4735,13 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req,
if (!username.empty() && !password.empty()) {
std::map<std::string, std::string> auth;
if (parse_www_authenticate(res, auth, is_proxy)) {
if (detail::parse_www_authenticate(res, auth, is_proxy)) {
Request new_req = req;
new_req.authorization_count_ += 1;
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
new_req.headers.erase(key);
new_req.headers.insert(make_digest_authentication_header(
req, auth, new_req.authorization_count_, random_string(10),
new_req.headers.insert(detail::make_digest_authentication_header(
req, auth, new_req.authorization_count_, detail::random_string(10),
username, password, is_proxy));
Response new_res;
@@ -5016,7 +5057,7 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
}
if (res.get_header_value("Connection") == "close" ||
res.version == "HTTP/1.0") {
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
stop_core();
}
@@ -5058,7 +5099,8 @@ inline Result ClientImpl::Get(const char *path, const Headers &headers,
req.progress = std::move(progress);
auto res = std::make_shared<Response>();
return Result{send(req, *res) ? res : nullptr, get_last_error()};
auto ret = send(req, *res);
return Result{ret ? res : nullptr, get_last_error()};
}
inline Result ClientImpl::Get(const char *path,
@@ -5121,7 +5163,8 @@ inline Result ClientImpl::Get(const char *path, const Headers &headers,
req.progress = std::move(progress);
auto res = std::make_shared<Response>();
return Result{send(req, *res) ? res : nullptr, get_last_error()};
auto ret = send(req, *res);
return Result{ret ? res : nullptr, get_last_error()};
}
inline Result ClientImpl::Head(const char *path) {
@@ -5136,7 +5179,8 @@ inline Result ClientImpl::Head(const char *path, const Headers &headers) {
req.path = path;
auto res = std::make_shared<Response>();
return Result{send(req, *res) ? res : nullptr, get_last_error()};
auto ret = send(req, *res);
return Result{ret ? res : nullptr, get_last_error()};
}
inline Result ClientImpl::Post(const char *path) {
@@ -5151,9 +5195,9 @@ inline Result ClientImpl::Post(const char *path, const std::string &body,
inline Result ClientImpl::Post(const char *path, const Headers &headers,
const std::string &body,
const char *content_type) {
return Result{send_with_content_provider("POST", path, headers, body, 0,
nullptr, content_type),
get_last_error()};
auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr,
content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Post(const char *path, const Params &params) {
@@ -5170,10 +5214,10 @@ inline Result ClientImpl::Post(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
return Result{send_with_content_provider("POST", path, headers, std::string(),
content_length, content_provider,
content_type),
get_last_error()};
auto ret = send_with_content_provider("POST", path, headers, std::string(),
content_length, content_provider,
content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Post(const char *path, const Headers &headers,
@@ -5225,9 +5269,9 @@ inline Result ClientImpl::Put(const char *path, const std::string &body,
inline Result ClientImpl::Put(const char *path, const Headers &headers,
const std::string &body,
const char *content_type) {
return Result{send_with_content_provider("PUT", path, headers, body, 0,
nullptr, content_type),
get_last_error()};
auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr,
content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Put(const char *path, size_t content_length,
@@ -5240,10 +5284,10 @@ inline Result ClientImpl::Put(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
return Result{send_with_content_provider("PUT", path, headers, std::string(),
content_length, content_provider,
content_type),
get_last_error()};
auto ret = send_with_content_provider("PUT", path, headers, std::string(),
content_length, content_provider,
content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Put(const char *path, const Params &params) {
@@ -5264,9 +5308,9 @@ inline Result ClientImpl::Patch(const char *path, const std::string &body,
inline Result ClientImpl::Patch(const char *path, const Headers &headers,
const std::string &body,
const char *content_type) {
return Result{send_with_content_provider("PATCH", path, headers, body, 0,
nullptr, content_type),
get_last_error()};
auto ret = send_with_content_provider("PATCH", path, headers, body, 0,
nullptr, content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Patch(const char *path, size_t content_length,
@@ -5279,10 +5323,10 @@ inline Result ClientImpl::Patch(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
return Result{send_with_content_provider("PATCH", path, headers,
std::string(), content_length,
content_provider, content_type),
get_last_error()};
auto ret = send_with_content_provider("PATCH", path, headers, std::string(),
content_length, content_provider,
content_type);
return Result{ret, get_last_error()};
}
inline Result ClientImpl::Delete(const char *path) {
@@ -5311,7 +5355,8 @@ inline Result ClientImpl::Delete(const char *path, const Headers &headers,
req.body = body;
auto res = std::make_shared<Response>();
return Result{send(req, *res) ? res : nullptr, get_last_error()};
auto ret = send(req, *res);
return Result{ret ? res : nullptr, get_last_error()};
}
inline Result ClientImpl::Options(const char *path) {
@@ -5326,7 +5371,8 @@ inline Result ClientImpl::Options(const char *path, const Headers &headers) {
req.path = path;
auto res = std::make_shared<Response>();
return Result{send(req, *res) ? res : nullptr, get_last_error()};
auto ret = send(req, *res);
return Result{ret ? res : nullptr, get_last_error()};
}
inline size_t ClientImpl::is_socket_open() const {
@@ -5799,7 +5845,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
if (!proxy_digest_auth_username_.empty() &&
!proxy_digest_auth_password_.empty()) {
std::map<std::string, std::string> auth;
if (parse_www_authenticate(res2, auth, true)) {
if (detail::parse_www_authenticate(res2, auth, true)) {
Response res3;
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
@@ -5807,8 +5853,8 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
Request req3;
req3.method = "CONNECT";
req3.path = host_and_port_;
req3.headers.insert(make_digest_authentication_header(
req3, auth, 1, random_string(10),
req3.headers.insert(detail::make_digest_authentication_header(
req3, auth, 1, detail::random_string(10),
proxy_digest_auth_username_, proxy_digest_auth_password_,
true));
return process_request(strm, req3, res3, false);
+87 -1
View File
@@ -43,6 +43,23 @@ TEST(StartupTest, WSAStartup) {
ASSERT_EQ(0, ret);
}
#endif
TEST(TrimTests, TrimStringTests) {
{
std::string s = "abc";
detail::trim(s);
EXPECT_EQ("abc", s);
}
{
std::string s = " abc ";
detail::trim(s);
EXPECT_EQ("abc", s);
}
{
std::string s = "";
detail::trim(s);
EXPECT_TRUE( s.empty() );
}
}
TEST(SplitTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
@@ -66,6 +83,24 @@ TEST(SplitTest, ParseQueryString) {
EXPECT_EQ("val3", dic.find("key3")->second);
}
TEST(SplitTest, ParseInvalidQueryTests) {
{
string s = " ";
Params dict;
detail::parse_query_text(s, dict);
EXPECT_TRUE(dict.empty());
}
{
string s = " = =";
Params dict;
detail::parse_query_text(s, dict);
EXPECT_TRUE(dict.empty());
}
}
TEST(ParseQueryTest, ParseQueryString) {
string s = "key1=val1&key2=val2&key3=val3";
Params dic;
@@ -1064,7 +1099,7 @@ protected:
})
.Post("/multipart",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(5u, req.files.size());
EXPECT_EQ(6u, req.files.size());
ASSERT_TRUE(!req.has_file("???"));
ASSERT_TRUE(req.body.empty());
@@ -1093,6 +1128,13 @@ protected:
EXPECT_EQ("application/octet-stream", file.content_type);
EXPECT_EQ(0u, file.content.size());
}
{
const auto &file = req.get_file_value("file4");
EXPECT_TRUE(file.filename.empty());
EXPECT_EQ(0u, file.content.size());
EXPECT_EQ("application/json tmp-string", file.content_type);
}
})
.Post("/empty",
[&](const Request &req, Response &res) {
@@ -1785,6 +1827,7 @@ TEST_F(ServerTest, MultipartFormData) {
{"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
{"file2", "{\n \"world\", true\n}\n", "world.json", "application/json"},
{"file3", "", "", "application/octet-stream"},
{"file4", "", "", " application/json tmp-string "}
};
auto res = cli_.Post("/multipart", items);
@@ -2157,6 +2200,49 @@ TEST_F(ServerTest, GetStreamedChunkedWithGzip2) {
EXPECT_EQ(200, res->status);
EXPECT_EQ(std::string("123456789"), res->body);
}
TEST(GzipDecompressor, ChunkedDecompression) {
std::string data;
for (size_t i = 0; i < 32 * 1024; ++i) {
data.push_back(static_cast<char>('a' + i % 26));
}
std::string compressed_data;
{
httplib::detail::gzip_compressor compressor;
bool result = compressor.compress(
data.data(),
data.size(),
/*last=*/true,
[&] (const char* data, size_t size) {
compressed_data.insert(compressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
}
std::string decompressed_data;
{
httplib::detail::gzip_decompressor decompressor;
// Chunk size is chosen specificaly to have a decompressed chunk size equal to 16384 bytes
// 16384 bytes is the size of decompressor output buffer
size_t chunk_size = 130;
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size(); chunk_begin += chunk_size) {
size_t current_chunk_size = std::min(compressed_data.size() - chunk_begin, chunk_size);
bool result = decompressor.decompress(
compressed_data.data() + chunk_begin,
current_chunk_size,
[&] (const char* data, size_t size) {
decompressed_data.insert(decompressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
}
}
ASSERT_EQ(data, decompressed_data);
}
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT